{"id":28440279,"url":"https://github.com/haxefoundation/record-macros","last_synced_at":"2025-06-27T10:31:23.396Z","repository":{"id":50236081,"uuid":"70372570","full_name":"HaxeFoundation/record-macros","owner":"HaxeFoundation","description":"Macro-based ORM (object-relational mapping)","archived":false,"fork":false,"pushed_at":"2023-09-25T17:24:11.000Z","size":117,"stargazers_count":48,"open_issues_count":11,"forks_count":25,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-06-06T03:41:53.127Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Haxe","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/HaxeFoundation.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2016-10-09T03:01:17.000Z","updated_at":"2025-03-06T07:25:07.000Z","dependencies_parsed_at":"2024-01-23T21:46:37.175Z","dependency_job_id":null,"html_url":"https://github.com/HaxeFoundation/record-macros","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/HaxeFoundation/record-macros","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HaxeFoundation%2Frecord-macros","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HaxeFoundation%2Frecord-macros/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HaxeFoundation%2Frecord-macros/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HaxeFoundation%2Frecord-macros/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/HaxeFoundation","download_url":"https://codeload.github.com/HaxeFoundation/record-macros/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HaxeFoundation%2Frecord-macros/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262238733,"owners_count":23280190,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-06-06T03:39:23.355Z","updated_at":"2025-06-27T10:31:23.383Z","avatar_url":"https://github.com/HaxeFoundation.png","language":"Haxe","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build Status](https://travis-ci.org/HaxeFoundation/record-macros.svg?branch=master)](https://travis-ci.org/HaxeFoundation/record-macros)\n\nRecord macros is a macro-based library that provides object-relational mapping to Haxe.\nWith `record-macros`, you can define some Classes that will map to your database tables. You can then manipulate tables like objects, by simply modifying the table fields and calling a method to update the datas or delete the entry. For most of the standard stuff, you only need to provide some basic declarations and you don't have to write one single SQL statement. You can later extend `record-macros` by adding your own SQL requests for some application-specific stuff.\n\n## Creating a Record\nYou can simply declare a `record-macros` Object by extending the sys.db.Object class :\n\n```haxe\nimport sys.db.Types;\n\nclass User extends sys.db.Object {\n    public var id : SId;\n    public var name : SString\u003c32\u003e;\n    public var birthday : SDate;\n    public var phoneNumber : SNull\u003cSText\u003e;\n}\n```\nAs you can see in this example, we are using special types declared in sys.db.Types in order to provide additional information for `record-macros`. Here's the list of supported types :\n\n  * `Null\u003cT\u003e, SNull\u003cT\u003e` : tells that this field can be NULL in the database\n  * `Int, SInt` : a classic 32 bits signed integer (SQL INT)\n  * `Float, SFloat` : a double precision float value (SQL DOUBLE)\n  * `Bool, SBool` : a boolean value (SQL TINYINT(1) or BOOL)\n  * `Date, SDateTime` : a complete date value (SQL DATETIME)\n  * `SDate` : a date-only value (SQL DATE)\n  * `SString\u003cK\u003e` : a size-limited string value (SQL VARCHAR(K))\n  * `String, SText` : a text up to 16 MB (SQL MEDIUMTEXT)\n  * `SBytes\u003cK\u003e` : a fixed-size bytes value (SQL BINARY(K))\n  * `SBinary, haxe.io.Bytes` : up to 16 MB bytes (SQL MEDIUMBLOB)\n  * `SId` : same as SInt but used as an unique ID with auto increment (SQL INT AUTO INCREMENT)\n  * `SEnum\u003cE\u003e` : a single enum without parameters which index is stored as a small integer (SQL TINYINT UNSIGNED)\n  * `SFlags\u003cE\u003e` : a 32 bits flag that uses an enum as bit markers. See EnumFlags\n  * `SData\u003cAnything\u003e` : allow arbitrary serialized data (see below)\n\n### Advanced Types\n\nThe following advanced types are also available if you want a more custom storage size :\n\n  * `SUInt` : an unsigned 32 bits integer (SQL UNSIGNED INT)\n  * `STinyInt / STinyUInt` : a small 8 bits signed/unsigned integer (SQL TINYINT)\n  * `SSmallInt / SSmallUInt` : a small 16 bits signed/unsigned integer (SQL SMALLINT)\n  * `SMediumIInt / SMediumUInt` : a small 24 bits signed/unsigned integer (SQL MEDIUMINT)\n  * `SBigInt` : a 64 bits signed integer (SQL BIGINT) - typed as Float in Haxe\n  * `SSingle` : a single precision float value (SQL FLOAT)\n  * `STinyText` : a text up to 255 bytes (SQL TINYTEXT)\n  * `SSmallText` : a text up to 65KB (SQL TEXT)\n  * `STimeStamp` : a 32-bits date timestamp (SQL TIMESTAMP)\n  * `SSmallBinary` : up to 65 KB bytes (SQL BLOB)\n  * `SLongBinary` : up to 4GB bytes (SQL LONGBLOB)\n  * `SUId` : same as SUInt but used as an unique ID with auto increment (SQL INT UNSIGNED AUTO INCREMENT)\n  * `SBigId` : same as SBigInt but used as an unique ID with auto increment (SQL BIGINT AUTO INCREMENT) - compiled as Float in Haxe\n  * `SSmallFlags\u003cE\u003e` : similar to SFlags except that the integer used to store the data is based on the number of flags allowed\n\n## Metadata\nYou can add Metadata to your `record-macros` class to declare additional informations that will be used by `record-macros`.\n\nBefore each class field :\n\n  * `@:skip` : ignore this field, which will not be part of the database schema\n  * `@:relation` : declare this field as a relation (see specific section below)\n\nBefore the `record-macros` class :\n\n  * `@:table(\"myTableName\")` : change the table name (by default it's the same as the class name)\n  * `@:id(field1,field2,...)` : specify the primary key fields for this table. For instance the following class does not have a unique id with auto increment, but a two-fields unique primary key :\n\n```haxe\n@:id(uid,gid)\nclass UserGroup extends sys.db.Object {\n    public var uid : SInt;\n    public var gid : SInt;\n}\n```\n\n  * `@:index(field1,field2,...,[unique])` : declare an index consisting of the specified classes fields - in that order. If the last field is unique then it means that's an unique index (each combination of fields values can only occur once)\n\n\n## Init/Cleanup\nThere are two static methods that you might need to call before/after using `record-macros` :\n\n  * `sys.db.Manager.initialize()` : will initialize the created managers. Make sure to call it at least once before using `record-macros`.\n  * `sys.db.Manager.cleanup()` : will cleanup the temporary object cache. This can be done if you are using server module caching to free memory or after a rollback to make sure that we don't use the cached object version.\n\n## Creating the Table\nAfter you have declared your table you can create it directly from code without writing SQL. All you need is to connect to your database, for instance by using sys.db.Mysql, then calling sys.db.TableCreate.create that will execute the CREATE TABLE SQL request based on the `record-macros` infos :\n\n```haxe\nvar cnx = sys.db.Mysql.connect({\n   host : \"localhost\",\n   port : null,\n   user : \"root\",\n   pass : \"\",\n   database : \"testBase\",\n   socket : null,\n});\nsys.db.Manager.cnx = cnx;\nif ( !sys.db.TableCreate.exists(User.manager) )\n{\n    sys.db.TableCreate.create(User.manager);\n}\n```\n\nPlease note that currently TableCreate will not create the index or initialize the relations of your table.\n\n## Insert\nIn order to insert a new `record-macros`, you can simply do the following :\n\n```haxe\nvar u = new User();\nu.name = \"Random156\";\nu.birthday = Date.now();\nu.insert();\n```\nAfter the `.insert()` is done, the auto increment unique id will be set and all fields that were null but not declared as nullable will be set to their default value (0 for numbers, \"\" for strings and empty bytes for binaries)\n\n## Manager\nEach `record-macros` object need its own manager. You can create your own manager by adding the following line to your `record-macros` class body :\n\n```haxe\npublic static var manager = new sys.db.Manager\u003cUser\u003e(User);\n```\nHowever, the `record-macros` Macros will do it automatically for you, so only add this if you want create your own custom Manager which will extend the default one.\n\n## Get\nIn order to retrieve an instance of your `record-macros`, you can call the manager get method by using the object unique identifier (primary key) :\n\n```haxe\nvar u = User.manager.get(1);\nif( u == null ) throw \"User #1 not found\";\ntrace(u.name);\n```\nIf you have a primary key with multiple values, you can use the following declaration :\n\n```haxe\nvar ug = UserGroup.manager.get({ uid : 1, gid : 2 });\n// ...\n```\n\n## Update/Delete\nOnce you have an instance of your `record-macros` object, you can modify its fields and call .update() to send these changes to the database :\n\n```haxe\nvar u = User.manager.get(1);\nif( u.phoneNumber == null ) u.phoneNumber = \"+3360000000\";\nu.update();\n```\nYou can also use `.delete()` to delete this object from the database :\n\n```haxe\nvar u = User.manager.get(1);\nif( u != null ) u.delete();\n```\n\n## Search Queries\nIf you want to search for some objects, you can use the `.manager.search` method :\n\n```haxe\nvar minId = 10;\nfor( u in User.manager.search($id \u003c minId) ) {\n    trace(u);\n}\n```\nIn order to differentiate between the database fields and the Haxe variables, all the database fields are prefixed with a dollar in search queries.\n\nSearch queries are checked at compiletime and the following SQL code is generated instead :\n\n```haxe\nunsafeSearch(\"SELECT * FROM User WHERE id \u003c \"+Manager.quoteInt(minId));\n```\nThe code generator also makes sure that no SQL injection is ever possible.\n\n## Syntax\nThe following syntax is supported :\n\n  * constants : integers, floats, strings, null, true and false\n  * all operations `+, -, *, /, %, |, \u0026, ^, \u003e\u003e, \u003c\u003c, \u003e\u003e\u003e`\n  * unary operations `!, - and ~`\n  * all comparisons : `== , \u003e= , \u003c=, \u003e, \u003c, !=`\n  * bool tests : `\u0026\u0026 , ||`\n  * parenthesis\n  * calls and fields accesses (compiled as Haxe expressions)\n\nWhen comparing two values with == or != and when one of them can be NULL, the SQL generator is using the \u003c=\u003e SQL operator to ensure that NULL == NULL returns true and NULL != NULL returns false.\n\n## Additional Syntax\nIt is also possible to use anonymous objects to match exact values for some fields (similar to previous `record-macros` but typed :\n\n```haxe\nUser.manager.search({ id : 1, name : \"Nicolas\" })\n// same as :\nUser.manager.search($id == 1 \u0026\u0026 $name == \"Nicolas\")\n// same as :\nUser.manager.search($id == 1 \u0026\u0026 { name : \"Nicolas\" })\n```\n\nYou can also use if conditions to generate different SQL based on Haxe variables (you cannot use database fields in if test) :\n\n```haxe\nfunction listName( ?name : String ) {\n    return User.manager.search($id \u003c 10 \u0026\u0026 if( name == null ) true else $name == name);\n}\n```\n\n## SQL operations\nYou can use the following SQL global functions in search queries :\n\n  * `$now() : SDateTime`, returns the current datetime (SQL NOW())\n  * `$curDate() : SDate`, returns the current date (SQL CURDATE())\n  * `$date(v:SDateTime) : SDate`, returns the date part of the DateTime (SQL DATE())\n  * `$seconds(v:Float) : SInterval`, returns the date interval in seconds (SQL INTERVAL v SECOND)\n  * `$minutes(v:Float) : SInterval`, returns the date interval in minutes (SQL INTERVAL v MINUTE)\n  * `$hours(v:Float) : SInterval`, returns the date interval in hours (SQL INTERVAL v HOUR)\n  * `$days(v:Float) : SInterval`, returns the date interval in days (SQL INTERVAL v DAY)\n  * `$months(v:Float) : SInterval`, returns the date interval in months (SQL INTERVAL v MONTH)\n  * `$years(v:Float) : SInterval`, returns the date interval in years (SQL INTERVAL v YEAR)\n\nYou can use the following SQL operators in search queries :\n\n  * `stringA.like(stringB)` : will use the SQL LIKE operator to find if stringB if contained into stringA\n\n## SQL IN\nYou can also use the Haxe in operator to get similar effect as SQL IN :\n\n```haxe\nUser.manager.search($name in [\"a\",\"b\",\"c\"]);\n```\nYou can pass any Iterable to the in operator. An empty iterable will emit a false statement to prevent sql errors when doing IN ().\n\n## Search Options\nAfter the search query, you can specify some search options :\n\n```haxe\n// retrieve the first 20 users ordered by ascending name\nUser.manager.search(true,{ orderBy : name, limit : 20 });\n```\n\nThe following options are supported :\n\n  * `orderBy` : you can specify one of several order database fields and use a minus operation in front of the field to indicate that you want to sort in descending order. For instance orderBy : [-name,id] will generate SQL ORDER BY name DESC, id\n  * `limit` : specify which result range you want to obtain. You can use Haxe variables and expressions in limit values, for instance : { limit : [pos,length] }\n  * `forceIndex` : specify that you want to force this search to use the specific index. For example to force a two-fields index use { forceIndex : [name,date] }. The index name used in that case will be TableName_name_date\n\n## Select/Count/Delete\nInstead of search you can use the `manager.select` method, which will only return the first result object :\n\n```haxe\nvar u = User.manager.select($name == \"John\");\n// ...\n```\nYou can also use the manager.count method to count the number of objects matching the given search query :\n\n```haxe\nvar n = User.manager.count($name.like(\"J%\") \u0026\u0026 $phoneNumber != null);\n// ...\n```\nYou can delete all objects matching the given query :\n\n```haxe\nUser.manager.delete($id \u003e 1000);\n```\n\n## Relations\nYou can declare relations between your database classes by using the @:relation metadata :\n\n```haxe\nclass User extends sys.db.Object {\n    public var id : SId;\n    // ....\n}\nclass Group extends sys.db.Object {\n   public var id : SId;\n   // ...\n}\n\n@:id(gid,uid)\nclass UserGroup extends sys.db.Object {\n    @:relation(uid) public var user : User;\n    @:relation(gid) public var group : Group;\n}\n```\nThe first time you read the user field from an UserGroup instance, `record-macros` will fetch the User instance corresponding to the current uid value and cache it. If you set the user field, it will modify the uid value as the same time.\n\n## Locking\nWhen using transactions, the default behavior for relations is that they are not locked. You can make there that the row is locked (SQL SELECT...FOR UPDATE) by adding the lock keyword after the relation key :\n\n```haxe\n@:relation(uid,lock) public var user : User;\n```\n\n## Cascading\nRelations can be strongly enforced by using CONSTRAINT/FOREIGN KEY with MySQL/InnoDB. This way when an User instance is deleted, all the corresponding UserGroup for the given user will be deleted as well.\n\nHowever if the relation field can be nullable, the value will be set to NULL.\n\nIf you want to enforce cascading for nullable-field relations, you can add the cascade keyword after the relation key :\n\n```haxe\n    @:relation(uid,cascade) var user : Null\u003cUser\u003e;\n```\n\n## Relation Search\nYou can search a given relation by using either the relation key or the relation property :\n\n```haxe\nvar user = User.manager.get(1);\nvar groups = UserGroup.manager.search($uid == user.id);\n// same as :\nvar groups = UserGroup.manager.search($user == user);\n```\n\nThe second case is more strictly typed since it does not only check that the key have the same type, and it also safer because it will use null id if the user value is null at runtime.\n\n## Dynamic Search\nIf you want to build at runtime you own exact-values search criteria, you can use manager.dynamicSearch that will build the SQL query based on the values you pass it :\n\n```haxe\nvar o = { name : \"John\", phoneNumber : \"+818123456\" };\nvar users = User.manager.dynamicSearch(o);\n```\nPlease note that you can get runtime errors if your object contain fields that are not in the database table.\n\n## Serialized Data\n\nIn order to store arbitrary serialized data in a `record-macros` object, you can use the SData type. For example :\n\n```\nimport sys.db.Types\nenum PhoneKind {\n    AtHome;\n    AtWork;\n    Mobile;\n}\nclass User extends sys.db.Object {\n    public var id : SId;\n    ...\n    public var phones : SData\u003cArray\u003c{ kind : PhoneKind, number : String }\u003e\u003e;\n}\n```\nWhen the phones field is accessed for reading (the first time only), it is unserialized. By default the data is stored as an haxe-serialized string, but you can override the doSerialize and doUnserialize methods of your Manager to have a specific serialization for a specific table or field\nWhen the phones field has been either read or written, a flag will be set to remember that potential changes were made\nWhen the `record-macros` object is either inserted or updated, the modified data is serialized and eventually sent to the database if some actual change have been done\nAs a consequence, pushing data into the phones Array or directly modifying the phone number will be noticed by the `record-macros` engine.\n\nThe SQL data type for SData is a binary blob, in order to allow any kind of serialization (text or binary), so the actual runtime value of the phones field is a Bytes. It will however only be accessible by reflection, since `record-macros` is changing the phones field into a property.\n\n## Accessing the record-macros Infos\nYou can get the database schema by calling the `.dbInfos()` method on the Manager. It will return a `sys.db.RecordInfos` structure.\n\n## Automatic Insert/Search/Edit Generation\nThe [dbadmin](https://github.com/ncannasse/dbadmin) project provides an HTML based interface that allows inserting/searching/editing and deleting `record-macros` objects based on the compiled `record-macros` information. It also allows database synchronization based on the `record-macros` schema by automatically detecting differences between the compile time schema and the current DB one.\n\n## Compatibility\nWhen using MySQL 5.7+, consider disabling [strict mode](https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sql-mode-strict). Record-macros do not provide sufficient checks (strings length,field default values...) to avoid errors in strict mode.\n\n## Running the unit tests\n\n```\n# clone and checkout\ngit clone https://github.com/HaxeFoundation/record-macros\ncd record-macros\n\n# prepare a test environment\nhaxelib newrepo\nhaxelib install all --always\n\n# install record-macros in dev mode\n# (necessary for extraParams.hxml to run)\nhaxelib dev record-macros .\n\n# optional compiler flags:\n#   -D UTEST_PATTERN=\u003cpattern\u003e  filter tests with pattern\n#   -D UTEST_PRINT_TESTS        print test names as they run\n#   -D UTEST_FAILURE_THROW      throw instead of report failures\n\nhaxe test-\u003ctarget\u003e.hxml\n\u003crun resulting program\u003e mysql://\u003cuser\u003e:\u003cpassword\u003e@\u003chost\u003e[:\u003cport\u003e]/\u003cdatabase\u003e\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhaxefoundation%2Frecord-macros","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhaxefoundation%2Frecord-macros","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhaxefoundation%2Frecord-macros/lists"}