Giter VIP home page Giter VIP logo

record-macros's Introduction

Build Status

Record macros is a macro-based library that provides object-relational mapping to Haxe. With 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.

Creating a Record

You can simply declare a record-macros Object by extending the sys.db.Object class :

import sys.db.Types;

class User extends sys.db.Object {
    public var id : SId;
    public var name : SString<32>;
    public var birthday : SDate;
    public var phoneNumber : SNull<SText>;
}

As 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 :

  • Null<T>, SNull<T> : tells that this field can be NULL in the database
  • Int, SInt : a classic 32 bits signed integer (SQL INT)
  • Float, SFloat : a double precision float value (SQL DOUBLE)
  • Bool, SBool : a boolean value (SQL TINYINT(1) or BOOL)
  • Date, SDateTime : a complete date value (SQL DATETIME)
  • SDate : a date-only value (SQL DATE)
  • SString<K> : a size-limited string value (SQL VARCHAR(K))
  • String, SText : a text up to 16 MB (SQL MEDIUMTEXT)
  • SBytes<K> : a fixed-size bytes value (SQL BINARY(K))
  • SBinary, haxe.io.Bytes : up to 16 MB bytes (SQL MEDIUMBLOB)
  • SId : same as SInt but used as an unique ID with auto increment (SQL INT AUTO INCREMENT)
  • SEnum<E> : a single enum without parameters which index is stored as a small integer (SQL TINYINT UNSIGNED)
  • SFlags<E> : a 32 bits flag that uses an enum as bit markers. See EnumFlags
  • SData<Anything> : allow arbitrary serialized data (see below)

Advanced Types

The following advanced types are also available if you want a more custom storage size :

  • SUInt : an unsigned 32 bits integer (SQL UNSIGNED INT)
  • STinyInt / STinyUInt : a small 8 bits signed/unsigned integer (SQL TINYINT)
  • SSmallInt / SSmallUInt : a small 16 bits signed/unsigned integer (SQL SMALLINT)
  • SMediumIInt / SMediumUInt : a small 24 bits signed/unsigned integer (SQL MEDIUMINT)
  • SBigInt : a 64 bits signed integer (SQL BIGINT) - typed as Float in Haxe
  • SSingle : a single precision float value (SQL FLOAT)
  • STinyText : a text up to 255 bytes (SQL TINYTEXT)
  • SSmallText : a text up to 65KB (SQL TEXT)
  • STimeStamp : a 32-bits date timestamp (SQL TIMESTAMP)
  • SSmallBinary : up to 65 KB bytes (SQL BLOB)
  • SLongBinary : up to 4GB bytes (SQL LONGBLOB)
  • SUId : same as SUInt but used as an unique ID with auto increment (SQL INT UNSIGNED AUTO INCREMENT)
  • SBigId : same as SBigInt but used as an unique ID with auto increment (SQL BIGINT AUTO INCREMENT) - compiled as Float in Haxe
  • SSmallFlags<E> : similar to SFlags except that the integer used to store the data is based on the number of flags allowed

Metadata

You can add Metadata to your record-macros class to declare additional informations that will be used by record-macros.

Before each class field :

  • @:skip : ignore this field, which will not be part of the database schema
  • @:relation : declare this field as a relation (see specific section below)

Before the record-macros class :

  • @:table("myTableName") : change the table name (by default it's the same as the class name)
  • @: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 :
@:id(uid,gid)
class UserGroup extends sys.db.Object {
    public var uid : SInt;
    public var gid : SInt;
}
  • @: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)

Init/Cleanup

There are two static methods that you might need to call before/after using record-macros :

  • sys.db.Manager.initialize() : will initialize the created managers. Make sure to call it at least once before using record-macros.
  • 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.

Creating the Table

After 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 :

var cnx = sys.db.Mysql.connect({
   host : "localhost",
   port : null,
   user : "root",
   pass : "",
   database : "testBase",
   socket : null,
});
sys.db.Manager.cnx = cnx;
if ( !sys.db.TableCreate.exists(User.manager) )
{
    sys.db.TableCreate.create(User.manager);
}

Please note that currently TableCreate will not create the index or initialize the relations of your table.

Insert

In order to insert a new record-macros, you can simply do the following :

var u = new User();
u.name = "Random156";
u.birthday = Date.now();
u.insert();

After 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)

Manager

Each record-macros object need its own manager. You can create your own manager by adding the following line to your record-macros class body :

public static var manager = new sys.db.Manager<User>(User);

However, 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.

Get

In order to retrieve an instance of your record-macros, you can call the manager get method by using the object unique identifier (primary key) :

var u = User.manager.get(1);
if( u == null ) throw "User #1 not found";
trace(u.name);

If you have a primary key with multiple values, you can use the following declaration :

var ug = UserGroup.manager.get({ uid : 1, gid : 2 });
// ...

Update/Delete

Once you have an instance of your record-macros object, you can modify its fields and call .update() to send these changes to the database :

var u = User.manager.get(1);
if( u.phoneNumber == null ) u.phoneNumber = "+3360000000";
u.update();

You can also use .delete() to delete this object from the database :

var u = User.manager.get(1);
if( u != null ) u.delete();

Search Queries

If you want to search for some objects, you can use the .manager.search method :

var minId = 10;
for( u in User.manager.search($id < minId) ) {
    trace(u);
}

In order to differentiate between the database fields and the Haxe variables, all the database fields are prefixed with a dollar in search queries.

Search queries are checked at compiletime and the following SQL code is generated instead :

unsafeSearch("SELECT * FROM User WHERE id < "+Manager.quoteInt(minId));

The code generator also makes sure that no SQL injection is ever possible.

Syntax

The following syntax is supported :

  • constants : integers, floats, strings, null, true and false
  • all operations +, -, *, /, %, |, &, ^, >>, <<, >>>
  • unary operations !, - and ~
  • all comparisons : == , >= , <=, >, <, !=
  • bool tests : && , ||
  • parenthesis
  • calls and fields accesses (compiled as Haxe expressions)

When comparing two values with == or != and when one of them can be NULL, the SQL generator is using the <=> SQL operator to ensure that NULL == NULL returns true and NULL != NULL returns false.

Additional Syntax

It is also possible to use anonymous objects to match exact values for some fields (similar to previous record-macros but typed :

User.manager.search({ id : 1, name : "Nicolas" })
// same as :
User.manager.search($id == 1 && $name == "Nicolas")
// same as :
User.manager.search($id == 1 && { name : "Nicolas" })

You can also use if conditions to generate different SQL based on Haxe variables (you cannot use database fields in if test) :

function listName( ?name : String ) {
    return User.manager.search($id < 10 && if( name == null ) true else $name == name);
}

SQL operations

You can use the following SQL global functions in search queries :

  • $now() : SDateTime, returns the current datetime (SQL NOW())
  • $curDate() : SDate, returns the current date (SQL CURDATE())
  • $date(v:SDateTime) : SDate, returns the date part of the DateTime (SQL DATE())
  • $seconds(v:Float) : SInterval, returns the date interval in seconds (SQL INTERVAL v SECOND)
  • $minutes(v:Float) : SInterval, returns the date interval in minutes (SQL INTERVAL v MINUTE)
  • $hours(v:Float) : SInterval, returns the date interval in hours (SQL INTERVAL v HOUR)
  • $days(v:Float) : SInterval, returns the date interval in days (SQL INTERVAL v DAY)
  • $months(v:Float) : SInterval, returns the date interval in months (SQL INTERVAL v MONTH)
  • $years(v:Float) : SInterval, returns the date interval in years (SQL INTERVAL v YEAR)

You can use the following SQL operators in search queries :

  • stringA.like(stringB) : will use the SQL LIKE operator to find if stringB if contained into stringA

SQL IN

You can also use the Haxe in operator to get similar effect as SQL IN :

User.manager.search($name in ["a","b","c"]);

You can pass any Iterable to the in operator. An empty iterable will emit a false statement to prevent sql errors when doing IN ().

Search Options

After the search query, you can specify some search options :

// retrieve the first 20 users ordered by ascending name
User.manager.search(true,{ orderBy : name, limit : 20 });

The following options are supported :

  • 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
  • limit : specify which result range you want to obtain. You can use Haxe variables and expressions in limit values, for instance : { limit : [pos,length] }
  • 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

Select/Count/Delete

Instead of search you can use the manager.select method, which will only return the first result object :

var u = User.manager.select($name == "John");
// ...

You can also use the manager.count method to count the number of objects matching the given search query :

var n = User.manager.count($name.like("J%") && $phoneNumber != null);
// ...

You can delete all objects matching the given query :

User.manager.delete($id > 1000);

Relations

You can declare relations between your database classes by using the @:relation metadata :

class User extends sys.db.Object {
    public var id : SId;
    // ....
}
class Group extends sys.db.Object {
   public var id : SId;
   // ...
}

@:id(gid,uid)
class UserGroup extends sys.db.Object {
    @:relation(uid) public var user : User;
    @:relation(gid) public var group : Group;
}

The 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.

Locking

When 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 :

@:relation(uid,lock) public var user : User;

Cascading

Relations 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.

However if the relation field can be nullable, the value will be set to NULL.

If you want to enforce cascading for nullable-field relations, you can add the cascade keyword after the relation key :

    @:relation(uid,cascade) var user : Null<User>;

Relation Search

You can search a given relation by using either the relation key or the relation property :

var user = User.manager.get(1);
var groups = UserGroup.manager.search($uid == user.id);
// same as :
var groups = UserGroup.manager.search($user == user);

The 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.

Dynamic Search

If 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 :

var o = { name : "John", phoneNumber : "+818123456" };
var users = User.manager.dynamicSearch(o);

Please note that you can get runtime errors if your object contain fields that are not in the database table.

Serialized Data

In order to store arbitrary serialized data in a record-macros object, you can use the SData type. For example :

import sys.db.Types
enum PhoneKind {
    AtHome;
    AtWork;
    Mobile;
}
class User extends sys.db.Object {
    public var id : SId;
    ...
    public var phones : SData<Array<{ kind : PhoneKind, number : String }>>;
}

When 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 When the phones field has been either read or written, a flag will be set to remember that potential changes were made When 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 As a consequence, pushing data into the phones Array or directly modifying the phone number will be noticed by the record-macros engine.

The 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.

Accessing the record-macros Infos

You can get the database schema by calling the .dbInfos() method on the Manager. It will return a sys.db.RecordInfos structure.

Automatic Insert/Search/Edit Generation

The 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.

Compatibility

When using MySQL 5.7+, consider disabling strict mode. Record-macros do not provide sufficient checks (strings length,field default values...) to avoid errors in strict mode.

Running the unit tests

# clone and checkout
git clone https://github.com/HaxeFoundation/record-macros
cd record-macros

# prepare a test environment
haxelib newrepo
haxelib install all --always

# install record-macros in dev mode
# (necessary for extraParams.hxml to run)
haxelib dev record-macros .

# optional compiler flags:
#   -D UTEST_PATTERN=<pattern>  filter tests with pattern
#   -D UTEST_PRINT_TESTS        print test names as they run
#   -D UTEST_FAILURE_THROW      throw instead of report failures

haxe test-<target>.hxml
<run resulting program> mysql://<user>:<password>@<host>[:<port>]/<database>

record-macros's People

Contributors

andyli avatar bablukid avatar constnw avatar jonasmalacofilho avatar nadako avatar realyuniquename avatar tobil4sk avatar waneck avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

record-macros's Issues

Unit tests fail with Haxe 4.0.0, hxcpp 4.0.38: Missing key foo

Had this problem in my project, therefore I ran the unit tests for record-macros.

With hexunit 0.30.0.
Haxe 3.4.7 with hxcpp 3.4.188 unit tests pass on both neko and hxcpp.
Haxe 4.0.0 with hxcpp 4.0.52 unit tests pass on neko and fail on hxcpp as follow:

hex/unittest/notifier/ConsoleNotifier.hx:47: <<< Start MySQLTest tests run >>>
hex/unittest/notifier/ConsoleNotifier.hx:76:    Test class 'MySQLTest'
hex/unittest/notifier/ConsoleNotifier.hx:102:           * [testDateQuery] .
hex/unittest/notifier/ConsoleNotifier.hx:104:                   hex.error.Exception | Missing key theId
hex/unittest/notifier/ConsoleNotifier.hx:105:                   Missing key theId:
hex/unittest/notifier/ConsoleNotifier.hx:102:           * [testIssue34] Test that cache management doesn't break @:skip fields
hex/unittest/notifier/ConsoleNotifier.hx:104:                   hex.error.Exception | Missing key theid
hex/unittest/notifier/ConsoleNotifier.hx:105:                   Missing key theid:
hex/unittest/notifier/ConsoleNotifier.hx:102:           * [testNull] .
hex/unittest/notifier/ConsoleNotifier.hx:104:                   hex.error.Exception | Missing key theId
hex/unittest/notifier/ConsoleNotifier.hx:105:                   Missing key theId:
hex/unittest/notifier/ConsoleNotifier.hx:102:           * [testIssue3828] .
hex/unittest/notifier/ConsoleNotifier.hx:104:                   hex.error.Exception | Missing key id
hex/unittest/notifier/ConsoleNotifier.hx:105:                   Missing key id:
hex/unittest/notifier/ConsoleNotifier.hx:102:           * [testEnum] .
hex/unittest/notifier/ConsoleNotifier.hx:104:                   hex.error.Exception | Missing key theId
hex/unittest/notifier/ConsoleNotifier.hx:105:                   Missing key theId:
hex/unittest/notifier/ConsoleNotifier.hx:102:           * [testStringIdRel] .
hex/unittest/notifier/ConsoleNotifier.hx:104:                   hex.error.Exception | Missing key name
hex/unittest/notifier/ConsoleNotifier.hx:105:                   Missing key name:
hex/unittest/notifier/ConsoleNotifier.hx:102:           * [testUpdate] .
hex/unittest/notifier/ConsoleNotifier.hx:104:                   hex.error.Exception | Missing key theId
hex/unittest/notifier/ConsoleNotifier.hx:105:                   Missing key theId:
hex/unittest/notifier/ConsoleNotifier.hx:102:           * [testData] .
hex/unittest/notifier/ConsoleNotifier.hx:104:                   hex.error.Exception | Missing key theId
hex/unittest/notifier/ConsoleNotifier.hx:105:                   Missing key theId:
hex/unittest/notifier/ConsoleNotifier.hx:102:           * [testSpodTypes] .
hex/unittest/notifier/ConsoleNotifier.hx:104:                   hex.error.Exception | Missing key theId
hex/unittest/notifier/ConsoleNotifier.hx:105:                   Missing key theId:
hex/unittest/notifier/ConsoleNotifier.hx:93:            * [testIssue6041]  25.93212890625ms
hex/unittest/notifier/ConsoleNotifier.hx:93:            * [testIssue19] Ensure that field types using full paths can be matched 34.487060546875ms
hex/unittest/notifier/ConsoleNotifier.hx:93:            * [testIssue6] Check that relations are not affected by the analyzer 0.112060546875ms
hex/unittest/notifier/ConsoleNotifier.hx:54: <<< End tests run >>>
hex/unittest/notifier/ConsoleNotifier.hx:55: Assertions passed: 8

hex/unittest/notifier/ConsoleNotifier.hx:59: Assertions failed: 9

This is the same exact bug I have in my app as well.

haxe.macro.Compiler.setFieldType

Hej !

Happy Christmas everyone !

I'm sorry, I always come with strange things...
Last changes in RecordMacros.hx related to :

var get = {
	args : [],
	params : [],
	ret : t,
	expr: macro return @:privateAccess $i{tname}.manager.__get(this, $v{f.name}, $v{relKey}, $v{lock}),
	//expr : Context.parse("return untyped "+tname+".manager.__get(this,'"+f.name+"','"+relKey+"',"+lock+")",pos),
};

Makes Haxe complain when trying to build my code.
I've done a small example, let's say we have that :
Main.hx

class Main{
	static var foo : Foo;
	public static function main(){}
}

Foo.hx

class Foo extends sys.db.Object{
	var id		: sys.db.Types.SId;
	@:relation( barID )
	public var bar	: Bar;
}

Bar.hx

class Bar extends sys.db.Object{
	var id : sys.db.Types.SId;
}

my/Bar.hx

package my;
class Bar extends sys.db.Object{
	var id	: sys.db.Types.SId;
	var b	: Bool;
}

build.hxml

-lib record-macros

#--macro haxe.macro.Compiler.setFieldType( "Foo", "bar", "my.Bar" )

-main Main
-php www

If this line is commented, all runs well, but when it's set, I get this error : record-macros/git/src/sys/db/RecordMacros.hx:1391: characters 46-53 : Unknown identifier : my.Bar

Is there something we could do to make it work again like before please ? (Before, untyped was used but now, with the strict synthax, the compiler complains...)

Thanks for reading,
Cheers,

Michal

PHP7 Initialization issue

Hej,

I get an error when I want to Serialize a relation on a SPOD object :
Uncaught ErrorException: Undefined property: user\User::$rights in /lib/Reflect.php:96
"rights" is a "relation" to UserRights class.
In the error output, I see Reflect.field is used, maybe it should do a Reflect.getProperty ?
But when I force Reflect.getProperty in the Manager instead of a Reflect.field, I get Memory Overflow.

Any ideas please ?

haxelib update

Haxelib update is 7 years old, and this repo is updated more recently

solution also might be to add this to the readme

haxelib git record-macros https://github.com/HaxeFoundation/record-macros

SEnum in SPOD not included in emitted SQL

I am attempting to use SPOD via record-macros in neko. When I use sys.db.Types.SEnum (for example to set a value on a new object to be inserted into the database), the generated SQL query does not include that field. In the example below I initialise a SpodRec with someInt : SInt = 4 and val : SEnum = value1, but when I attempt to insert the record, the generated SQL is INSERT INTO SpodRec (someInt) VALUES (4) where I would expect it to be INSERT INTO SpodRec (someInt, val) VALUES (4,0) or similar. Am I using this incorrectly?

class SpodTest {
	public static function main(){
		trace("SpodTesting");
		var cnx = sys.db.Sqlite.open("spodtest.db");
		sys.db.Manager.cnx = cnx;
		sys.db.Manager.initialize();

		//Create the SpodRec table
		if ( !sys.db.TableCreate.exists( SpodRec.manager ) )
			sys.db.TableCreate.create( SpodRec.manager );

		var mySpodRec = new SpodRec();
		mySpodRec.someInt = 4;
		mySpodRec.val = value1;
		mySpodRec.insert();
	}
}

class SpodRec extends sys.db.Object {
	public var id : sys.db.Types.SId;
	public var someInt : sys.db.Types.SInt;
	public var val : sys.db.Types.SEnum<MyEnum>;
}

enum MyEnum {
	value1;
	value2;
	value3;
}

Output:

SpodTest.hx:5: SpodTesting                                                                                                                
Called from ? line 1                                                                                                                      
Called from SpodTest.hx line 17
Called from sys/db/Object.hx line 53
Called from sys/db/Manager.hx line 199
Called from sys/db/Manager.hx line 441
Called from /usr/share/haxe/std/neko/_std/sys/db/Sqlite.hx line 40
Uncaught exception - Error while executing INSERT INTO SpodRec (someInt) VALUES (4) (Sqlite error : SQL logic error)

Abstract enums

Hej,
When using SPOD I "search" a row using an abstract enum which is an Int, so I do that : MyObject.manager.select( $target == target ), where target is an abstract enum (Int) and the compiler complains because it expects an Int, is it SPOD related or do I miss something please ?
Note : I have to do explicit cast( target, Int) in order to make it works.

SEnum field as table @id

Following is an old issue from SPOD, that is still present in this library.
See HaxeFoundation/haxe#4144

Say I use an simple record :

import sys.db.Types;
import sys.db.Manager;

@:id( lang )
class Locale extends sys.db.Object
{
	public static var manager = new Manager<Locale>(Locale);

	public var lang:SEnum<LocaleString>;
	public var locale:SString<5>;
}

enum LocaleString
{
	fr;
	en;
	/*TODO es;	de;*/
}

While compiling to php, no error happens, but at runtime, I get this error :

uncaught exception: Missing key lang

in file: /project/02-cms/out/lib/sys/db/Manager.class.php line 701
#0 /project/02-cms/out/lib/sys/db/Manager.class.php(727): sys_db_Manager->makeCacheKey(Object(model_db_Locale))
#1 /project/02-cms/out/lib/sys/db/Manager.class.php(448): sys_db_Manager->addToCache(Object(model_db_Locale))
#2 /project/02-cms/out/lib/sys/db/Manager.class.php(531): sys_db_Manager->cacheObject(Object(_hx_anonymous), true)
#3 /project/02-cms/out/lib/controller/command/CreatePageFromTemplate.class.php(23): sys_db_Manager->unsafeObjects('SELECT * FROM L...', true)
#4 /project/02-cms/out/lib/controller/command/CreateIndexPage.class.php(16): controller_command_CreatePageFromTemplate->init('LVS, des expert...')
#5 /project/02-cms/out/lib/controller/Router.class.php(170): controller_command_CreateIndexPage->__construct(Object(model_db_LocaleString))
#6 /project/02-cms/out/lib/controller/Router.class.php(156): controller_Router->createPageFromURL(Object(model_db_LocaleString), '', NULL)
#7 /project/02-cms/out/lib/Reflect.class.php(27): controller_Router->doFr('', NULL, NULL)
#8 /project/02-cms/out/lib/haxe/web/Dispatch.class.php(66): Reflect::callMethod(Object(controller_Router), Array, Object(_hx_array))
#9 /project/02-cms/out/lib/Main.class.php(18): haxe_web_Dispatch->runtimeDispatch(Object(_hx_anonymous))
#10 /project/02-cms/out/index.php(11): Main::main()
#11 {main}

Here is the haxe code that produce the trouble :

var changeLocale
	= switch( Router.locale )
	{
		case LocaleString.fr : Locale.manager.select( $lang != LocaleString.fr ).lang;
		case LocaleString.en : Locale.manager.select( $lang != LocaleString.en ).lang;
	}

After long tests, I narrowed the issue : the lang field that is used as an @id.
In order to fix it, change the key to the other field of the table.
Like so :

@:id( locale )
class Locale extends sys.db.Object
{
	public static var manager = new Manager<Locale>(Locale);

	public var lang:SEnum<LocaleString>;
	public var locale:SString<5>;
}

HTH

Unit tests failing with PHP7

When I was working on my last PR ( adding tests for PHP7 , and for mysql 5.6 ), @andyli recommanded to comment some tests which were failing with PHP7.
https://github.com/HaxeFoundation/record-macros/blob/master/test/MySQLTest.hx#L110
https://github.com/HaxeFoundation/record-macros/blob/master/test/SQLiteTest.hx#L406
https://github.com/HaxeFoundation/record-macros/blob/master/test/SQLiteTest.hx#L483

So, currently , even if there is a "build passing" label , this library is not working well with PHP7.

merged PR :
#26
#27

Bug in manager.search(true)

Text.manager.search(true);

fails with:

Uncaught exception - Error while executing SELECT * FROM Text WHERE TRUE (Sqlite error in SELECT * FROM Text WHERE TRUE : no such column: TRUE)

on Haxe 3.4.0 on neko target with latest haxelib version of record-macros.

Haxe 4 updates

Hej,

There are some issues using Haxe 4 preview :
-https://github.com/HaxeFoundation/record-macros/blob/master/src/sys/db/RecordMacros.hx#L470 : C:\HaxeToolkit\haxe\lib\record-macros/git/src/sys/db/RecordMacros.hx:470: characters 13-20 : Warning : private structure fields are deprecated
-https://github.com/HaxeFoundation/record-macros/blob/master/src/sys/db/RecordMacros.hx#L1108 : C:\HaxeToolkit\haxe\std/haxe/macro/Context.hx:558: characters 3-8 : Uncaught exception This method is no longer supported. See HaxeFoundation/haxe#5746

For the first one it is easy to fix but what about onMacroContextReused ? I don't know how to fix that.
I don't know how can I help someone to fix all that please ?
(There are some issues for hx3compat too, it would be great to update all that also in order to make it work with Haxe 4)

Behavior changed with MySQL 5.7

Since I upgraded to MySQL 5.7, I encountered unexpected problems with record macros.
The bugs are coming from MySQL who becomes more strict with v5.7, but it could be nice to update record-macros accordingly.

string fields maxlength
Before 5.7 the strings length were trimmed by Mysql to the max length. Now it triggers an error like
"UPDATE Vendor SET city = 'La Roque Sainte Margueritte' WHERE id = 8881 Data too long for column 'city' at row 1"

Record macros could now trim automatically fields to the max length to avoid exceptions.

field nullity
Before 5.7 , it was possible to set a field to null on non-nullable fields. MySQL was falling back to a default value : null strings becomes "", null integer becomes 0...
Now it triggers an error like "UPDATE User SET pass = null, cdate = '2018-08-07 00:00:00' WHERE id = 46461 Column 'pass' cannot be null"

Record macros could manage to fall back to default values.

any suggestions ?

Arbitrary Serialized Data

I was reading the readme and taking notes on the library. Everything seems rather straightforward, and the examples are concise except for in the case of Arbitrary Serialized Data section.

For example, using this snippet:

import sys.db.Types
enum PhoneKind {
    AtHome;
    AtWork;
    Mobile;
}
class User extends sys.db.Object {
    public var id : SId;
    ...
    public var phones : SData<Array<{ kind : PhoneKind, number : String }>>;
}

I understand that the phones member would have to be reflected into a Bytes array, how would I then convert this into an Array of typedef pairs of PhoneKinds and Strings ?

Sorry if this is a novice question, and not really an issue, wasn't really sure where else to inquire about this.

Haxe 4.0.0-preview.3 errors

  • record-macros/1,0,0-alpha/src/sys/db/RecordMacros.hx:736: characters 12-14 : Unmatched patterns: OpIn

  • record-macros/1,0,0-alpha/src/sys/db/RecordMacros.hx:902: characters 8-11 : Identifier 'EIn' is not part of haxe.macro.ExprDef (Suggestion: EIf)

  • record-macros/1,0,0-alpha/src/sys/db/RecordMacros.hx:902: characters 8-17 : Unrecognized pattern: EIn(e, v)

Synchronizing the fields

Hej all !

I've encoured a weired bug using Haxe/PHP7. Then I tried on Haxe/PHP5 and all worked fine until I tried to "trace" the fields being deleted in this line : https://github.com/HaxeFoundation/record-macros/blob/master/src/sys/db/Manager.hx#L672
I haven't a global view of what is going on, but reading this code I can see that we delete all the fields of the member class (not only the one used in the DB) and then we just set the fields used in the DB.
I think we should just delete the fields used in the DB (not these @:skip ones).
And then, I think it's unusefull to delete them because we set them in the next lines.
All that to say that when we remove the lines 672 and 673, there is no more bugs for that case.
Do you think I'm right or maybe I miss something ?

Thanks for reading,

We should provide a release on haxelib

Hi, I think we should provide a release on haxelib.
So far there is only one version of record-macros:

Name: record-macros
Tags: db, orm, spod, sql
Desc: Macro-based ORM (object-relational mapping)
Website: https://github.com/HaxeFoundation/record-macros
License: MIT
Owner: waneck
Version: 1.0.0-alpha
Releases: 
   2016-10-09 03:40:11 1.0.0-alpha : Initial release

To make this worst this 2016 is broken, it can not get passed compilation even for the simplest tests:

record-macros/1.0.0-alpha/haxelib/src/sys/db/RecordMacros.hx:467: characters 13-20 : Warning : private structure fields are deprecated                                         
record-macros/1.0.0-alpha/haxelib/src/sys/db/RecordMacros.hx:736: characters 12-14 : Unmatched patterns: OpIn   

The git version OTOH works.
So if we don't want people to diverge from record-macros, we should release some version on haxelib soon, one that's advertised to work with Haxe4+ for example.

SPOD sys.db.Manager cacheObject Neko/PHP inconsistency

Hej,

I don't know if it has been solved yet, but as it's not longer maintened by Haxe, I repost it here (original post : HaxeFoundation/haxe#5636)

I'm sorry I've just tested that on Neko and PHP targets playing with DBAdmin first, and I get inconsistency here between the 2 targets :
https://github.com/HaxeFoundation/haxe/blob/development/std/sys/db/Manager.hx#L390
It seems that when creating an empty instance on Neko like that :
untyped __dollar__new(x); where x is an object with fields/values, it fills these fields automatically on the class instance.
But on the others targets this one is used :
Type.createEmptyInstance( My ClassName ); Here it doesn't fill the class fields, it's ok, but then in this cacheObject function, there miss this fill.
So I added that :
Reflect.setField( o, f, val ); just after https://github.com/HaxeFoundation/haxe/blob/development/std/sys/db/Manager.hx#L401

So at the end it gives that :

function cacheObject( x : T, lock : Bool ) {
        #if neko
        var o = untyped __dollar__new(x);
        untyped __dollar__objsetproto(o, class_proto.prototype);
        #else
        var o : T = Type.createEmptyInstance(cast class_proto);
        untyped o._manager = this;
        #end
        normalizeCache(x);
        for (f in Reflect.fields(x) )
        {
            var val:Dynamic = Reflect.field(x,f), info = table_infos.hfields.get(f);
                        Reflect.setField( o, f, val );
            if (info != null)
            {
                var fieldName = getFieldName(info);
                Reflect.setField(o, fieldName, val);
            }
        }
        Reflect.setField(o,cache_field,x);
        addToCache(o);
        untyped o._lock = lock;
        return o;
    }

Now all is working fine and DBAdmin gives me all the fields like on Neko target.

I digged to see where the differences between Neko and PHP come from exactly, I've been until sys.db.Manager where in the cacheObject function, the returned created instance of the class is not filled correctly on others targets than Neko. Maybe it should be fixed there ? I don't know because like you, I've seen this bug only on dbadmin for the moment...

could you do a haxelib release please? it does not compile with 3.4.5 but its recommended by haxe compiler

Hello,
when we compile with haxe 3.4.5 lots of warnings recommend to use your lib.
but if we get the latests release with haxelib install the code does not compile.
only for a very obscure reason:
the code of this release is exactly the same than in haxe 3.4.5 std library except for two lines that are the reason of the compilation error:
HaxeToolkit\3.4.5\lib\record-macros/1,0,0-alpha/src/sys/db/RecordMacros.hx:785: characters 8-12 : Capture variables must be lower-case

the case OpIn: line but OpIn does not exist in haxe 3.4.5

to make the haxe 3.4.5 adoption easier, could you please release a lib version that is working fine with haxe 3.4.5 please? a perfect copy of the standard lib code without warnings, at least, or the latests code version, if its passing all tests.

thanks

Unicode. Would we need a `@:unicode` meta?

Had prematurely proposed a simple PR #52 to add a test about unicode, since I was experiencing problems and no documentation about it.

Current target is php+mysql.
Turns out I find why characters were shown as "????", it's because I needed to set the Table and the Column to utf8mb4.

Since I don't think this commands a pull request after all, I will share it here, and maybe it can open discussion about whether there is any better solution (something not requesting an ALTER request):

   // this was supposed to go in test/CommonDatabaseTest.hx
    public function testUnicode() {
        // without these two lines the test passes,
        // but content is garbage like "?????????" 
        Manager.cnx.request("ALTER TABLE NullableSpodClass CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci");
        Manager.cnx.request("ALTER TABLE NullableSpodClass MODIFY string VARCHAR(250) CHARACTER SET utf8mb4");

        for (sUnicode in [ 
            "ﺎﻠﻋﺮﺒﻳﺓ", // Arabic
            "汉语",    // Chinese
        ]) { 
            var n1 = getDefaultNull();
            n1.string = sUnicode;
            n1.insert();

            var n2 = NullableSpodClass.manager.select($theId == n1.theId);
            Assert.isTrue( n2 != null );
            Assert.equals( n2.string, sUnicode );
        } 
    } 

I am imagining 'maybe' a @:unicode markup could be used so that the ALTER lines are unneeded. But this is probably a long topic. Meanwhile at least this is searchable if anybody has an issue.

Foreign key or setter bug since haxe 3.4.2

I installed yesterday haxe 3.4.2 after using 3.2.1 for a long time ( yes ... I know )
Therefore I installed the record-macros lib.

Allmost all the INSERT operations in my app are broken when they involve relations / foreign key.

In the example code below, the Transaction records should be stored with a userId and groupId.
The bug is that groupId is 0 , instead of the correct group id.
My guess is that when I set a relation from another relation (i.e : t.group = u.group; ) the setter doesn't work, since it was working perfectly in haxe 3.2.1

In my real-life-application, which has foreign key constraints ( managed by dbadmin lib ) and gives me MySQL errors like this "INSERT INTO Contract (name,startDate,endDate,distributorNum,flags,type,userId,vendorId) VALUES ('Commande Aromatiques', '2017-03-22 13:24:23', '2018-03-22 00:00:00', 0, 0, 1, 6459, 849) Cannot add or update a child row: a foreign key constraint fails (cagette.Contract, CONSTRAINT Contract_amap FOREIGN KEY (amapId) REFERENCES Amap (id) ON DELETE CASCADE)"

`
package;
import sys.db.Types;

class SpodBug
{

public static function main(){
	
	var cnx = sys.db.Mysql.connect({
	   host : "localhost",
	   port : null,
	   user : "cagette",
	   pass : "cagette",
	   database : "test",
	   socket : null,
	});
	
	sys.db.Manager.cnx = cnx;
	
	sys.db.Manager.initialize();
	
	trace("cnx : "+cnx);
	
	create(User.manager);
	create(Group.manager);
	create(Transaction.manager);
	
	var g = new Group();
	g.name = "the winnerz";
	g.insert();
	
	var u = new User();
	u.name = "Bob";
	u.group = g;
	u.insert();
	
	var t = new Transaction();
	t.user = u;
	t.group = u.group; // THIS WONT WORK
	t.insert();
	
	trace(t.toString());
	
}

static function create( m  ){
	if ( !sys.db.TableCreate.exists(m) ){
		trace("creating table "+ m.dbInfos().name);
		sys.db.TableCreate.create(m);
	}
}

}

class User extends sys.db.Object {
public var id : SId;
public var name : SString<32>;
@:relation(groupId) public var group : Group;

}
class Group extends sys.db.Object {
public var id : SId;
public var name : SString<32>;

}

class Transaction extends sys.db.Object {
public var id : SId;

@:relation(userId) 	public var user : User;
@:relation(groupId) public var group : Group;

override public function toString(){
	return "transaction " + id + " of " + user +" from " + group;
}

}
`

Haxe 4 problem

Hej,
I get this error with Haxe 4 preview 4 and latest nightly build :
C:\HaxeToolkit\haxe\lib\record-macros/git/src/sys/db/RecordMacros.hx:470: characters 13-20 : Warning : private structure fields are deprecated

Problem with reference data going through Serializer/Unserializer

Hej !

I ran into a strange bug last days, I'm on it for 2-3 days trying to isolate it.
Let's say we have this :

class A
extends sys.db.Object
{
	var id : SId;

	@:relation( bID )
	public var b	: B;

	public function new(){
		super();
	}
}

class B
extends sys.db.Object
{
	var id : SId;

	@:relation( aID )
	public var a	: A;
	
	public function new(){
		super();
	}
}
class Main{
    static function main(){
        var a	= new A();
	var b	= new B();
	a.b	= b;
	b.a	= a;
		
	trace( a == a.b.a );                                            // <--------------------------- true

	var ser		= Serializer.run( a );
	var a2	: A     = Unserializer.run( ser );

	trace( a2 == a2.b.a );                                          // <--------------------------- true
    }
}
class Main{
    static function main(){
       var a	= A.manager.get( 1 );
		
	trace( a == a.b.a );                                             // <--------------------------- true

	var ser		= Serializer.run( a );
	var a2	: A     = Unserializer.run( ser );

	trace( a2 == a2.b.a );                                           // <--------------------------- false !!!
    }
}

My bug isn't that but when digging, I noticed this strange behaviour and I think my bug could come from that.
My real bug comes from lock arg in .unsafeObject(), when set to false something is going wrong and all works fine when it's true. Maybe it can help you... or maybe it will mess. Anyway, the pasted code here is my question here.

lock behaviour

Hej Jonas,

I hope you're in a good mood because I've another headacke for you...
I don't know if it's really a bug or maybe just a behaviour that should be more explicitly described, I'll try to explain with the minimalest example that I could do.
Let's say we have that :

This C class is just used to make the bug rise : As noted, this get_as function must be called (I think it's because of the A references...)

class C extends sys.db.Object {
	var id	: SId;

	@:skip
	public var as	(get,null)	: List<A>;

	public function new() {
		super();
	}

	function get_as(){
		return A.manager.search( $c == this );		// <--- If not called, all is ok even if lock == false
	}
}

This A class has a special hxSerialize and hxUnserialize function to get a custom serialization, which consist of having a field called xfields that will just have the fields to serialize.
And in get_bs() I do a call to upper class in order to rise the bug

class A extends sys.db.Object{
	@:skip
	public var xfields	(get,null): Array<String>;
	
	var id	: SId;
	
	@:relation( cID )
	public var c	: C;

	@:skip
	public var bs	(get,null)	: Array<B>;

	public function new() {
		super();
	}

	function get_bs(){
		var tmp	= c.as;		// <------- to get the call, then the bug
		return bs	= Lambda.array( B.manager.search( $a == this ) );
	}

	//

	function get_xfields(){
		if( xfields == null ) xfields	= [ "id", "c" ];
		return xfields;
	}

	@:keep
	public function hxSerialize( s : haxe.Serializer ) {
		s.serialize( xfields.join( "," ) );
		for ( f in xfields ) {
			s.serialize( Reflect.getProperty( this, f ) );
		}
	}

	@:keep
	public function hxUnserialize( u : haxe.Unserializer ) {
		var sxfields	: String = u.unserialize();
		xfields = sxfields.split( "," );
		for ( f in xfields ) {
			Reflect.setProperty( this, f, u.unserialize() );
		}
	}
}

Nothing special here...

class B extends sys.db.Object {
	var id	: SId;
	
	@:relation( aID )
	public var a	: A;

	public function new() {
		super();
	}
}

And now the Main class (I'm sorry I tried your quick statements to create the table without success so I've done it using INSERT...:

class Main {
    static function main(){
        haxe.Serializer.USE_CACHE = true;
	sys.db.Manager.cnx = sys.db.Sqlite.open(":memory:");
	sys.db.Manager.cnx.request("CREATE TABLE A AS SELECT 1 id, 1 cID");
        sys.db.Manager.cnx.request("INSERT INTO `A` (`id`, `cID`) VALUES (2, 1), (3, 1)");
	sys.db.Manager.cnx.request("CREATE TABLE B AS SELECT 1 id, 1 aID");
        sys.db.Manager.cnx.request("CREATE TABLE C AS SELECT 1 id");
        
        var list	= Lambda.array( A.manager.unsafeObjects( "SELECT * FROM A WHERE cID = 1", false ) );	// <---- If lock == false then bug
	for( o in list ){
		o.xfields.push( "bs" );
		trace( o.xfields );
	}
	var ser	= Serializer.run( list );
	var userList : Array<A>	= Unserializer.run( ser );

	for( o in userList ){
		trace( o.xfields );
	}
    }
}

I'm sorry for this "swamp" but I would like to understand what is going on here.

As you'll see in the traces, if lock is true, then the unserialized objects have all their xfields filled, but if lock is false, only one of the unserialized objects will have the bs string added to its xfields.
Then it's interesting to note that if there is no that var tmp = c.as; // <------- to get the call, then the bug in the A class, all is working fine even if lock is false.

If you could explain me what is going on, if it's a normal behaviour or if it's a bug please ?

Thanks Jonas !

Are the tests working with haxe4?

I made a quick fix here for haxe 4 (in is a Binop now) and I tried to run the tests with:
haxe test.hxml but I get errors like:
test/MySpodClass.hx:30: characters 26-68 : Relation type should be a sys.db.Object
Any idea why?

Library errors with haxe version 4.0 preview 2

Errors from RecordMacros

D:/haxe/lib/record-macros/1,0,0-alpha/src/sys/db/RecordMacros.hx:736: characters 12-14 : Unmatched patterns: OpIn
D:/haxe/lib/record-macros/1,0,0-alpha/src/sys/db/RecordMacros.hx:902: characters 8-17 : Not enough arguments
D:/haxe/lib/record-macros/1,0,0-alpha/src/sys/db/Manager.hx:31: characters 13-19 : Build failure

String concatenation broken in SQLite

record-macros attempts to use the MySQL CONCAT function for all string concatenation, which is not available in SQLite (it uses the || concat operator)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.