README

Filemaker Server connector for golang

This library provide access to Filemaker Server using XML Web publishing

It's mirror of repo on Bitbucket!

This library is a port of https://github.com/PerfectlySoft/Perfect-FileMaker, but in golang.

In Production

We use this lib in our company in production for some public APIs and feature migration to other DBs like Postgres. It's some kind of bridge.

API could change and sometimes might not be documented well. So look for commits and updates.

Tests

FileMaker is not Postgres or MySQL so we cannot run docker and test it everywhere. May be we could run EC2 with Windows and installed FileMaker server on it. If you have any ideas, open new issue or contact me.

Install

go get bitbucket.org/amanbolat/gofmcon

then in your code

import "bitbucket.org/amanbolat/gofmcon"

Example

In main.go

package main

import (
    fm "bitbucket.org/amanbolat/gofmcon"
    "log"
    "github.com/kelseyhightower/envconfig"
    "fmt"
    "errors"
)

type config struct {
    FmHost          string `split_words:"true" required:"true"`
    FmUser          string `split_words:"true" required:"true"`
    FmPort          string `split_words:"true" required:"true"`
    FmDatabaseName  string `split_words:"true" required:"true"`
    FmPass          string `split_words:"true" required:"true"`
}

type postStore struct {
    fmConn *fm.FMConnector
    dbName string
}

type Post struct {
    Author string
    Title string
    Content string
}

func (p *Post) Populate(record *fm.Record) {
    p.Author = record.Field("author")
    p.Title = record.Field("title")
    p.Content = record.Field("content")
}

func main() {
    var conf = &config{}
    err := envconfig.Process("", conf)
    if err != nil {
        log.Fatal(err)
    }
    
    fmConn := fm.NewFMConnector(conf.FmHost, conf.FmPort, conf.FmUser, conf.FmPass)
    store := postStore{fmConn: fmConn, dbName: conf.FmDatabaseName}
    
    posts, err := store.GetAllPosts(fmConn)
    if err != nil {                                    
        log.Fatal(err)
    }
    
    fmt.Print(posts)
}

func (ps *postStore) GetAllPosts(conn *fm.FMConnector) (*[]Post, error) {
	var posts []Post

	q := fm.NewFMQuery(ps.dbName, "Posts_list", fm.FindAll)  // Create query
	fmset, err := ps.fmConn.Query(q)                        // Make request with query
	if err != nil {                                         // Check for errors
		return &posts, errors.New("Failed get posts: " + err.Error())
	}

	for _, r := range fmset.Resultset.Records[0:] {         // Iterate through records
		p := Post{}
		p.Populate(&r)                                      // Populate it with record
		posts = append(posts, p)
	}

	return &posts, nil                                      // Return posts
}

Get single record

    q := fm.NewFMQuery(databaseName, layout_name, fm.Find)
    q.WithFields(
        fm.FMQueryField{Name: "field_name", Value: "001", Op: fm.Equal},
    ).WithMaxRecords(1)

Get filemaker error

    fmSet, err := r.conn.Query(q)
    if err != nil {
        if err.Error() == fmt.Sprintf("filemaker_error: %s", fm.FileMakerErrorCodes[401]) {
            // your code
        }
    
        // else do something
    }

Create record

    q := fm.NewFMQuery(databaseName, layout_name, fm.New)
    q.WithFields(
        fm.FMQueryField{Name: "field_name", Value: "some_value"},
    )
    
    fmSet, err := fmConn.Query(q)

Sort by some field

    q.WithSortFields(fm.FMSortField{Name: "some_field", Order: fm.Descending})

Update record

Your object should have FileMaker record id to update record in database. Please see more in FileMaker documentation.

    q := fm.NewFMQuery(databaseName, layout_name, fm.Edit)
    q.WithFields(
        fm.FMQueryField{Name: "field_name", Value: "some_new_value"},
    )
    q.WithRecordId(updated_object.FMRecordID)

Use some script

    // SCRIPT_DELIMITER can be '|', '_' or any other symbol for
    // On FileMaker side input should be parsed to get every parameter
    // that is why we need delimiter
    q.WithPostFindScripts("script_name")
    q.WithScriptParams(SCRIPT_DELIMITER, param_1, param_2, param_3)

Container fields

You should use ContainerField method to get slice of container field urls(image, file...) or it could be base64 encoded content.

    containerURLs := append(containerURLs, fmRecord.ContainerField("container_field_name")...)

#TODO

  • Add tests
  • Add methods to get information about database and layouts
  • Add documentation
Expand ▾ Collapse ▴

Documentation

Index

Constants

View Source
const (
	FMDBNames = "-dbnames"
)

Variables

View Source
var FileMakerErrorCodes = map[int]string{}/* 243 elements not displayed */

Functions

func WithAmp

func WithAmp(s string) string

Types

type DataSource

type DataSource struct {
	Database        string `xml:"database"`
	DateFormat      string `xml:"date_format"`
	Layout          string `xml:"layout"`
	Table           string `xml:"table"`
	TimeFormat      string `xml:"time-format"`
	TimestampFormat string `xml:"timestamp-format"`
	TotalCount      int    `xml:"total-count"`
}

type FMAction

type FMAction string
const (
	Find      FMAction = "-findquery"
	FindAll   FMAction = "-findall"
	FindAny   FMAction = "-findany"
	New       FMAction = "-new"
	Edit      FMAction = "-edit"
	Delete    FMAction = "-delete"
	Duplicate FMAction = "-dup"
)

func (FMAction) String

func (a FMAction) String() string

type FMConnector

type FMConnector struct {
	Host     string
	Port     string
	Username string
	Password string
	Client   *http.Client
}

func NewFMConnector

func NewFMConnector(host string, port string, username string, password string) *FMConnector

func (*FMConnector) Ping

func (fmc *FMConnector) Ping() error

func (*FMConnector) Query

func (fmc *FMConnector) Query(q *FMQuery) (FMResultset, error)

type FMError

type FMError struct {
	Code int `xml:"code,attr"`
}

func (*FMError) Error

func (e *FMError) Error() string

func (*FMError) String

func (e *FMError) String() string

type FMFieldOp

type FMFieldOp string
const (
	Equal            FMFieldOp = "eq"
	Contains         FMFieldOp = "cn"
	BeginsWith       FMFieldOp = "bw"
	EndsWith         FMFieldOp = "ew"
	GreaterThan      FMFieldOp = "gt"
	GreaterThanEqual FMFieldOp = "gte"
	LessThan         FMFieldOp = "lt"
	LessThanEqual    FMFieldOp = "lte"
)

type FMLogicalOp

type FMLogicalOp string
const (
	And FMLogicalOp = "and"
	Or  FMLogicalOp = "or"
	Not FMLogicalOp = "not"
)

type FMQuery

type FMQuery struct {
	Database              string
	Layout                string
	Action                FMAction
	QueryFields           []FMQueryFieldGroup
	SortFields            []FMSortField
	RecordId              int // default should be -1
	PreSortScripts        []string
	PreFindScripts        []string
	PostFindScripts       []string
	ScriptParams          []string
	ScriptParamsDelimiter string
	ResponseLayout        string
	ResponseFields        []string
	MaxRecords            int // default should be -1
	SkipRecords           int // default should be 0
	Query                 map[string]string
}

func NewFMQuery

func NewFMQuery(database string, layout string, action FMAction) *FMQuery

func (*FMQuery) QueryString

func (q *FMQuery) QueryString() string

func (*FMQuery) WithFieldGroups

func (q *FMQuery) WithFieldGroups(fieldGroups ...FMQueryFieldGroup) *FMQuery

func (*FMQuery) WithFields

func (q *FMQuery) WithFields(fields ...FMQueryField) *FMQuery

func (*FMQuery) WithMaxRecords

func (q *FMQuery) WithMaxRecords(max int) *FMQuery

func (*FMQuery) WithPostFindScripts

func (q *FMQuery) WithPostFindScripts(scripts ...string) *FMQuery

func (*FMQuery) WithPreFindScripts

func (q *FMQuery) WithPreFindScripts(scripts ...string) *FMQuery

func (*FMQuery) WithPreSortScripts

func (q *FMQuery) WithPreSortScripts(scripts ...string) *FMQuery

func (*FMQuery) WithRecordId

func (q *FMQuery) WithRecordId(id int) *FMQuery

func (*FMQuery) WithResponseFields

func (q *FMQuery) WithResponseFields(fields ...string) *FMQuery

func (*FMQuery) WithResponseLayout

func (q *FMQuery) WithResponseLayout(lay string) *FMQuery

func (*FMQuery) WithScriptParams

func (q *FMQuery) WithScriptParams(delimiter string, params ...string) *FMQuery

func (*FMQuery) WithSkipRecords

func (q *FMQuery) WithSkipRecords(skip int) *FMQuery

func (*FMQuery) WithSortFields

func (q *FMQuery) WithSortFields(sortFields ...FMSortField) *FMQuery

type FMQueryField

type FMQueryField struct {
	Name  string
	Value string
	Op    FMFieldOp
}

func (*FMQueryField) ValueWithOp

func (qf *FMQueryField) ValueWithOp() string

type FMQueryFieldGroup

type FMQueryFieldGroup struct {
	Op     FMLogicalOp
	Fields []FMQueryField
}

func (*FMQueryFieldGroup) SimpleFieldsString

func (fg *FMQueryFieldGroup) SimpleFieldsString() string

type FMResultset

type FMResultset struct {
	Resultset  *Resultset  `xml:"resultset"`
	DataSource *DataSource `xml:"datasource"`
	Version    string      `xml:"version,attr"`
	FMError    FMError     `xml:"error"`
}

func (*FMResultset) HasError

func (rs *FMResultset) HasError() bool

type FMSortField

type FMSortField struct {
	Name  string
	Order FMSortOrder
}

type FMSortOrder

type FMSortOrder string
const (
	Ascending  FMSortOrder = "ascend"
	Descending FMSortOrder = "descend"
	Custom     FMSortOrder = "custom"
)

func (FMSortOrder) String

func (so FMSortOrder) String() string

type Field

type Field struct {
	FieldName string   `xml:"name,attr" json:"fieldName"`
	FieldData []string `xml:"data" json:"fieldData"`
}

type Record

type Record struct {
	ID     int      `xml:"record-id,attr"`
	Fields []*Field `xml:"field"`

	RelatedSet []*RelatedSet `xml:"relatedset"`
	// contains filtered or unexported fields
}

func (*Record) ContainerField

func (r *Record) ContainerField(name string) []string

    ContainerField returns all data of container field

    func (*Record) Field

    func (r *Record) Field(name string) string

      Field returns fields first data if exists

      func (*Record) RelatedSetFromTable

      func (r *Record) RelatedSetFromTable(t string) *RelatedSet

      type RelatedSet

      type RelatedSet struct {
      	Count   int       `xml:"count,attr"`
      	Table   string    `xml:"table,attr"`
      	Records []*Record `xml:"record"`
      }

      type Resultset

      type Resultset struct {
      	Count   int       `xml:"count,attr" json:"count"`
      	Fetched int       `xml:"fetch-size,attr" json:"fetched"`
      	Records []*Record `xml:"record"`
      }