Setup

Import

In any of the source files where you intend to use with this module, add the following import:

import PerfectFileMaker

Overview

The main objects that you will be working with when accessing FileMaker databases are described here.

struct FileMakerServer

This is the main interface to a FileMaker Server. Before accessing any database you will need to create an instance of this struct and provide a server host name or IP address, a port, and a valid username and password for the server. If the given port is 443 then all requests will be encrypted HTTPS.

Once the server connection is instantiated you can perform four different operations. These are:

list available databases

list a database's layouts

list a layout's fields

perform a query

In all cases you provide a callback and the operation occurs asynchronously. When the operation has completed the callback is called and given a closure which, when executed, will either return the operation response object or throw an error. The type of the operation response object will differ depending on the type of operation. The exception error object provides access to the error code and accompanying message string. It will be of the following type:

When listing database or layout names, the response object will simply be an array of strings. When retrieving layout information, the response object will be a FMPLayoutInfo object. When performing a query the response object will be a FMPResultSet.

struct FMPLayoutInfo

FMPLayoutInfo is defined as follows:

/// Represents meta-information about a particular layout.
public struct FMPLayoutInfo {
/// Each field or related set as a list.
public let fields: [FMPMetaDataItem]
/// Each field or related set keyed by name.
public let fieldsByName: [String:FMPFieldType]
}

This object contains all of the information pertaining to the fields on a layout. This includes the field names and types and also indicates which portals/relations are on the layout and which fields are contained within them.

Field information is provided in two different ways. The first is represented by an array of FMPMetaDataItems. This enum value indicates if the item is a regular field or a portal/relation. The second is a dictionary containing the full field name and the field type. If the field is in a relation it will be named with the standard FileMaker PortalName::FieldName syntax.

What follows are the definitions for FMPMetaDataItem, FMPFieldDefinition & FMPFieldType.

/// Represents either an individual field definition or a related (portal) definition.
public enum FMPMetaDataItem {
/// An individual field.
case fieldDefinition(FMPFieldDefinition)
/// A related set. Indicates the portal name and its contained fields.
case relatedSetDefinition(String, [FMPFieldDefinition])
}

/// A FileMaker field definition. Indicates a field name and type.
public struct FMPFieldDefinition {
/// The field name.
public let name: String
/// The field type.
public let type: FMPFieldType
}

struct FMPResultSet

An FMPResultSet object contains the result of a FileMaker query. This includes database & layout meta information, as well as the number of records which were found in total and a each record in the found set.

/// The result set produced by a query.
public struct FMPResultSet {
/// Database meta-info.
public let databaseInfo: FMPDatabaseInfo
/// Layout meta-info.
public let layoutInfo: FMPLayoutInfo
/// The number of records found by the query.
public let foundCount: Int
/// The list of records produced by the query.
public let records: [FMPRecord]
}

In addition to the previously described FMPLayoutInfo struct, a result set also contains the following bits of database related information:

/// Meta-information for a database.
public struct FMPDatabaseInfo {
/// The date format indicated by the server.
public let dateFormat: String
/// The time format indicated by the server.
public let timeFormat: String
/// The timestamp format indicated by the server.
public let timeStampFormat: String
/// The total number of records in the database.
public let recordCount: Int
}

The found record data is accessed through the FMPResultSet.records property. This array of FMPRecords will contain one element for each returned record.

/// An individual result set record.
public struct FMPRecord {
/// A type of record item.
public enum RecordItem {
/// An individual field.
case field(String, FMPFieldValue)
/// A related set containing a list of related records.
case relatedSet(String, [FMPRecord])
}
/// The record id.
public let recordId: Int
/// The contained record items keyed by name.
public let elements: [String:RecordItem]
}

A record holds each field or portal item keyed by name in its elements property. The values in this dictionary are FMPRecord.RecordItem enum objects. These objects indicate if the item is a regular field, in which case its name and value can be directly accessed, or if the item is a related record set. If the item is a related record set then the portal name and an array of nested records are provided.

In either case a field's value is represented through the FMPFieldValue struct.

Queries

The FileMakerServer.query function lets you search for sets of records or manipulate individual records. You specify a query using a FMPQuery struct. This struct is then formatted as a FileMaker XML query string and sent to the FileMaker Server. The server returns its response as XML. This XML is parsed and converted to a FMPResultSet object.

A query is created by instantiating a FMPQuery struct and then adding options to it. Each option add will return a new modified object. In this manner options can be "chained" to construct the desired final query object. The query object is then given to the FileMakerServer.query function and the results are generated.

A FMPQuery is first instantiated with a database name, layout name and action. The possible actions are:

/// A database action.
public enum FMPAction: CustomStringConvertible {
/// Perform a search given the current query.
case find
/// Find all records in the database.
case findAll
/// Find and retrieve a random record.
case findAny
/// Create a new record given the current query data.
case new
/// Edit (update) the record indicated by the record id with the current query fields/values.
case edit
/// Delete the record indicated by the current record id.
case delete
/// Duplicate the record indicated by the current record id.
case duplicate
}

Many of the FMPQuery functions accepts Strings or Ints and are self-explanatory. Any function which accepts an array can be called multiple times and the new values will be appended to the existing values.

When performing an .edit, .delete or .duplicate action a record id must be set by calling FMPQuery.recordId(_ recordId: Int). Setting a record id when performing a .find will retrieve only the indicated record.

It is possible to search on one layout but return fields from another. Call FMPQuery.responseLayout(_ responseLayout: String) to set the response layout. By default the layout which is searched on will be the response layout.

It is possible and advisable, for performance reasons, to return only the minimum of required fields in a result. The names of the fields you want returned in the result can be set through the FMPQuery.responseFields(_ responseFields: [String]) function.

Sorting and the usage of query fields are detailed below.

Sorting

Records in a result set can be returned sorted by indicating one or more sort fields and sort orders. This is accomplished by calling the FMPQuery.sortFields function and passing the desired fields and orders as an array of FMPSortField objects. FMPSortField uses the FMPSortOrder enum to indicate the sort order. Both are defined as follows:

/// A record sort order.
public enum FMPSortOrder: CustomStringConvertible {
/// Sort the records by the indicated field in ascending order.
case ascending
/// Sort the records by the indicated field in descending order.
case descending
/// Sort the records by the indicated field in a custom order.
case custom
}
/// A sort field indicator.
public struct FMPSortField {
/// The name of the field on which to sort.
public let name: String
/// A field sort order.
public let order: FMPSortOrder
/// Initialize with a field name and sort order.
public init(name: String, order: FMPSortOrder)
/// Initialize with a field name using the default FMPSortOrder.ascending sort order.
public init(name: String)
}

Query Fields

Query fields are added to a FMPQuery to indicate either fields which should be modified, in the case of an .edit action or fields which should be searched on, in the case of a .find action. Query fields hold a field name along with a corresponding value. In the case of the .find action, query fields also hold a field level operator. These operators represent a relation of a field to the indicated value. For example, a field operator could indicate that a field's contents should be greater-than-or-equal to the value when performing a search. The default field level operator is begins-with (as is standard for FileMaker database searches).

Individual query fields are represented by FMPQueryField objects. Field level operators are represented by FMPFieldOp. These are defined as follows:

When performing an .edit action, FMPQueryFields can be added to a FMPQuery as an array through the FMPQuery.queryFields(_ queryFields: [FMPQueryField]) function. Any field level operators are ignored in an .edit.

When performing a .find query fields are grouped by logical operator. Logical operators indicate how the fields should be treated together in a query. The possible logical operators are: and, or, not. Their meanings, when performing a search and selecting records for the result set, are:

and: All query fields in the group must match for the record to be selected.

or: Any field in the group can match and the record will be selected.

not: If all query fields in the group match then the record will be omitted from the result set.

Multiple query field groups can be added to a FMPQuery. Each group will be considered in-order when FileMaker performs the search.

Logical operators and query field groups are represented by FMPLogicalOp & FMPQueryFieldGroup, respectively.

/// A logical operator used with query field groups.
public enum FMPLogicalOp {
case and, or, not
}
/// A group of query fields.
public struct FMPQueryFieldGroup {
/// The logical operator for the field group.
public let op: FMPLogicalOp
/// The list of fiedls in the group.
public let fields: [FMPQueryField]
/// Initialize with an operator and field list.
/// The default logical operator is FMPLogicalOp.and.
public init(fields: [FMPQueryField], op: FMPLogicalOp = .and)
}

Query field groups are added through the FMPQuery.queryFields(_ queryFields: [FMPQueryFieldGroup]) function.

Examples

The following code snippets illustrate the basic activities that one would perform against FileMaker databases.

List Available Databases

This snippet connects to the server and has it list all of the hosted databases.