first commit
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
/test.n
|
||||||
|
/test.sqlite
|
||||||
|
/.vscode
|
||||||
|
lib/*
|
||||||
|
/index.php
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
language: haxe
|
||||||
|
sudo: true
|
||||||
|
|
||||||
|
haxe:
|
||||||
|
- development
|
||||||
|
- 3.4.7
|
||||||
|
- 3.4.2
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
#install php 7.2 on ubuntu trusty
|
||||||
|
- sudo add-apt-repository ppa:ondrej/php -y
|
||||||
|
- sudo apt-get update
|
||||||
|
- sudo apt-get install -y php7.2
|
||||||
|
#create database in mysql5.6
|
||||||
|
- mysql -e 'CREATE DATABASE IF NOT EXISTS test;'
|
||||||
|
|
||||||
|
services:
|
||||||
|
- mysql
|
||||||
|
|
||||||
|
install:
|
||||||
|
- haxelib install all --always
|
||||||
|
|
||||||
|
script:
|
||||||
|
- haxe test.hxml
|
||||||
|
- neko test.n mysql://travis:@127.0.0.1/test
|
||||||
|
- php -v
|
||||||
|
- haxe testPHP.hxml
|
||||||
|
- php index.php mysql://travis:@127.0.0.1/test
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2016 Haxe Foundation
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,357 @@
|
|||||||
|
[](https://travis-ci.org/HaxeFoundation/record-macros)
|
||||||
|
|
||||||
|
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 :
|
||||||
|
|
||||||
|
```haxe
|
||||||
|
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 :
|
||||||
|
|
||||||
|
```haxe
|
||||||
|
@: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 :
|
||||||
|
|
||||||
|
```haxe
|
||||||
|
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 :
|
||||||
|
|
||||||
|
```haxe
|
||||||
|
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 :
|
||||||
|
|
||||||
|
```haxe
|
||||||
|
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) :
|
||||||
|
|
||||||
|
```haxe
|
||||||
|
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 :
|
||||||
|
|
||||||
|
```haxe
|
||||||
|
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 :
|
||||||
|
|
||||||
|
```haxe
|
||||||
|
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 :
|
||||||
|
|
||||||
|
```haxe
|
||||||
|
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 :
|
||||||
|
|
||||||
|
```haxe
|
||||||
|
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 :
|
||||||
|
|
||||||
|
```haxe
|
||||||
|
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 :
|
||||||
|
|
||||||
|
```haxe
|
||||||
|
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) :
|
||||||
|
|
||||||
|
```haxe
|
||||||
|
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 :
|
||||||
|
|
||||||
|
```haxe
|
||||||
|
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 :
|
||||||
|
|
||||||
|
```haxe
|
||||||
|
// 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 :
|
||||||
|
|
||||||
|
```haxe
|
||||||
|
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 :
|
||||||
|
|
||||||
|
```haxe
|
||||||
|
var n = User.manager.count($name.like("J%") && $phoneNumber != null);
|
||||||
|
// ...
|
||||||
|
```
|
||||||
|
You can delete all objects matching the given query :
|
||||||
|
|
||||||
|
```haxe
|
||||||
|
User.manager.delete($id > 1000);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Relations
|
||||||
|
You can declare relations between your database classes by using the @:relation metadata :
|
||||||
|
|
||||||
|
```haxe
|
||||||
|
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 :
|
||||||
|
|
||||||
|
```haxe
|
||||||
|
@: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 :
|
||||||
|
|
||||||
|
```haxe
|
||||||
|
@: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 :
|
||||||
|
|
||||||
|
```haxe
|
||||||
|
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 :
|
||||||
|
|
||||||
|
```haxe
|
||||||
|
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](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.
|
||||||
|
|
||||||
|
## Compatibility
|
||||||
|
When 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.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "record-macros",
|
||||||
|
"url": "https://github.com/HaxeFoundation/record-macros",
|
||||||
|
"license": "MIT",
|
||||||
|
"classPath": "src",
|
||||||
|
"tags":["db","spod","orm","sql"],
|
||||||
|
"description": "Macro-based ORM (object-relational mapping)",
|
||||||
|
"version": "1.0.0-alpha",
|
||||||
|
"releasenote": "Initial release",
|
||||||
|
"contributors":["andyli","ncannasse","simn","waneck"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,723 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C)2005-2016 Haxe Foundation
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
* copy of this software and associated documentation files (the "Software"),
|
||||||
|
* to deal in the Software without restriction, including without limitation
|
||||||
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
* and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
* Software is furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
* DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package sys.db;
|
||||||
|
import Reflect;
|
||||||
|
import sys.db.Connection;
|
||||||
|
import sys.db.RecordInfos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Record Manager : the persistent object database manager. See the tutorial on
|
||||||
|
Haxe website to learn how to use Record.
|
||||||
|
**/
|
||||||
|
#if !macro @:build(sys.db.RecordMacros.addRtti()) #end
|
||||||
|
class Manager<T : Object> {
|
||||||
|
|
||||||
|
/* ----------------------------- STATICS ------------------------------ */
|
||||||
|
public static var cnx(default, set) : Connection;
|
||||||
|
public static var lockMode : String;
|
||||||
|
|
||||||
|
private static inline var cache_field = "__cache__";
|
||||||
|
|
||||||
|
private static var object_cache : haxe.ds.StringMap<Object> = new haxe.ds.StringMap();
|
||||||
|
private static var init_list : List<Manager<Dynamic>> = new List();
|
||||||
|
|
||||||
|
private static var KEYWORDS = {
|
||||||
|
var h = new haxe.ds.StringMap();
|
||||||
|
for( k in "ADD|ALL|ALTER|ANALYZE|AND|AS|ASC|ASENSITIVE|BEFORE|BETWEEN|BIGINT|BINARY|BLOB|BOTH|BY|CALL|CASCADE|CASE|CHANGE|CHAR|CHARACTER|CHECK|COLLATE|COLUMN|CONDITION|CONSTRAINT|CONTINUE|CONVERT|CREATE|CROSS|CURRENT_DATE|CURRENT_TIME|CURRENT_TIMESTAMP|CURRENT_USER|CURSOR|DATABASE|DATABASES|DAY_HOUR|DAY_MICROSECOND|DAY_MINUTE|DAY_SECOND|DEC|DECIMAL|DECLARE|DEFAULT|DELAYED|DELETE|DESC|DESCRIBE|DETERMINISTIC|DISTINCT|DISTINCTROW|DIV|DOUBLE|DROP|DUAL|EACH|ELSE|ELSEIF|ENCLOSED|ESCAPED|EXISTS|EXIT|EXPLAIN|FALSE|FETCH|FLOAT|FLOAT4|FLOAT8|FOR|FORCE|FOREIGN|FROM|FULLTEXT|GRANT|GROUP|HAVING|HIGH_PRIORITY|HOUR_MICROSECOND|HOUR_MINUTE|HOUR_SECOND|IF|IGNORE|IN|INDEX|INFILE|INNER|INOUT|INSENSITIVE|INSERT|INT|INT1|INT2|INT3|INT4|INT8|INTEGER|INTERVAL|INTO|IS|ITERATE|JOIN|KEY|KEYS|KILL|LEADING|LEAVE|LEFT|LIKE|LIMIT|LINES|LOAD|LOCALTIME|LOCALTIMESTAMP|LOCK|LONG|LONGBLOB|LONGTEXT|LOOP|LOW_PRIORITY|MATCH|MEDIUMBLOB|MEDIUMINT|MEDIUMTEXT|MIDDLEINT|MINUTE_MICROSECOND|MINUTE_SECOND|MOD|MODIFIES|NATURAL|NOT|NO_WRITE_TO_BINLOG|NULL|NUMERIC|ON|OPTIMIZE|OPTION|OPTIONALLY|OR|ORDER|OUT|OUTER|OUTFILE|PRECISION|PRIMARY|PROCEDURE|PURGE|READ|READS|REAL|REFERENCES|REGEXP|RELEASE|RENAME|REPEAT|REPLACE|REQUIRE|RESTRICT|RETURN|REVOKE|RIGHT|RLIKE|SCHEMA|SCHEMAS|SECOND_MICROSECOND|SELECT|SENSITIVE|SEPARATOR|SET|SHOW|SMALLINT|SONAME|SPATIAL|SPECIFIC|SQL|SQLEXCEPTION|SQLSTATE|SQLWARNING|SQL_BIG_RESULT|SQL_CALC_FOUND_ROWS|SQL_SMALL_RESULT|SSL|STARTING|STRAIGHT_JOIN|TABLE|TERMINATED|THEN|TINYBLOB|TINYINT|TINYTEXT|TO|TRAILING|TRIGGER|TRUE|UNDO|UNION|UNIQUE|UNLOCK|UNSIGNED|UPDATE|USAGE|USE|USING|UTC_DATE|UTC_TIME|UTC_TIMESTAMP|VALUES|VARBINARY|VARCHAR|VARCHARACTER|VARYING|WHEN|WHERE|WHILE|WITH|WRITE|XOR|YEAR_MONTH|ZEROFILL|ASENSITIVE|CALL|CONDITION|CONNECTION|CONTINUE|CURSOR|DECLARE|DETERMINISTIC|EACH|ELSEIF|EXIT|FETCH|GOTO|INOUT|INSENSITIVE|ITERATE|LABEL|LEAVE|LOOP|MODIFIES|OUT|READS|RELEASE|REPEAT|RETURN|SCHEMA|SCHEMAS|SENSITIVE|SPECIFIC|SQL|SQLEXCEPTION|SQLSTATE|SQLWARNING|TRIGGER|UNDO|UPGRADE|WHILE".split("|") )
|
||||||
|
h.set(k.toLowerCase(),true);
|
||||||
|
h;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function set_cnx( c : Connection ) {
|
||||||
|
cnx = c;
|
||||||
|
lockMode = (c != null && c.dbName() == "MySQL") ? " FOR UPDATE" : "";
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------- BASIC API ----------------------------- */
|
||||||
|
|
||||||
|
var table_infos : RecordInfos;
|
||||||
|
var table_name : String;
|
||||||
|
var table_keys : Array<String>;
|
||||||
|
var class_proto : { prototype : Dynamic };
|
||||||
|
|
||||||
|
public function new( classval : Class<T> ) {
|
||||||
|
var m : Array<Dynamic> = haxe.rtti.Meta.getType(classval).rtti;
|
||||||
|
if( m == null ) throw "Missing @rtti for class " + Type.getClassName(classval);
|
||||||
|
table_infos = haxe.Unserializer.run(m[0]);
|
||||||
|
table_name = quoteField(table_infos.name);
|
||||||
|
table_keys = table_infos.key;
|
||||||
|
// set the manager and ready for further init
|
||||||
|
class_proto = cast classval;
|
||||||
|
#if neko
|
||||||
|
class_proto.prototype._manager = this;
|
||||||
|
init_list.add(this);
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
|
public function all( ?lock: Bool ) : List<T> {
|
||||||
|
return unsafeObjects("SELECT * FROM " + table_name,lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public macro function get(ethis,id,?lock:haxe.macro.Expr.ExprOf<Bool>) : #if macro haxe.macro.Expr #else haxe.macro.Expr.ExprOf<T> #end {
|
||||||
|
return RecordMacros.macroGet(ethis,id,lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public macro function select(ethis, cond, ?options, ?lock:haxe.macro.Expr.ExprOf<Bool>) : #if macro haxe.macro.Expr #else haxe.macro.Expr.ExprOf<T> #end {
|
||||||
|
return RecordMacros.macroSearch(ethis, cond, options, lock, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public macro function search(ethis, cond, ?options, ?lock:haxe.macro.Expr.ExprOf<Bool>) : #if macro haxe.macro.Expr #else haxe.macro.Expr.ExprOf<List<T>> #end {
|
||||||
|
return RecordMacros.macroSearch(ethis, cond, options, lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public macro function count(ethis, cond) : #if macro haxe.macro.Expr #else haxe.macro.Expr.ExprOf<Int> #end {
|
||||||
|
return RecordMacros.macroCount(ethis, cond);
|
||||||
|
}
|
||||||
|
|
||||||
|
public macro function delete(ethis, cond, ?options) : #if macro haxe.macro.Expr #else haxe.macro.Expr.ExprOf<Void> #end {
|
||||||
|
return RecordMacros.macroDelete(ethis, cond, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function dynamicSearch( x : {}, ?lock : Bool ) : List<T> {
|
||||||
|
var s = new StringBuf();
|
||||||
|
s.add("SELECT * FROM ");
|
||||||
|
s.add(table_name);
|
||||||
|
s.add(" WHERE ");
|
||||||
|
addCondition(s,x);
|
||||||
|
return unsafeObjects(s.toString(),lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
function quote( s : String ) : String {
|
||||||
|
return getCnx().quote( s );
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------- RECORDOBJECT API -------------------------- */
|
||||||
|
|
||||||
|
function doUpdateCache( x : T, name : String, v : Dynamic ) {
|
||||||
|
var cache : { v : Dynamic } = Reflect.field(x, "cache_" + name);
|
||||||
|
// if the cache has not been fetched (for instance if the field was set by reflection)
|
||||||
|
// then we directly use the new value
|
||||||
|
if( cache == null )
|
||||||
|
return v;
|
||||||
|
var v = doSerialize(name, cache.v);
|
||||||
|
// don't set it since the value might change again later
|
||||||
|
// Reflect.setField(x, name, v);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function getFieldName(field:RecordField):String
|
||||||
|
{
|
||||||
|
return switch (field.t) {
|
||||||
|
case DData | DEnum(_):
|
||||||
|
"data_" + field.name;
|
||||||
|
case _:
|
||||||
|
field.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function doInsert( x : T ) {
|
||||||
|
unmake(x);
|
||||||
|
var s = new StringBuf();
|
||||||
|
var fields = new List();
|
||||||
|
var values = new List();
|
||||||
|
var cache = Reflect.field(x,cache_field);
|
||||||
|
if (cache == null)
|
||||||
|
{
|
||||||
|
Reflect.setField(x,cache_field,cache = {});
|
||||||
|
}
|
||||||
|
|
||||||
|
for( f in table_infos.fields ) {
|
||||||
|
var name = f.name,
|
||||||
|
fieldName = getFieldName(f);
|
||||||
|
var v:Dynamic = Reflect.field(x,fieldName);
|
||||||
|
if( v != null ) {
|
||||||
|
fields.add(quoteField(name));
|
||||||
|
switch( f.t ) {
|
||||||
|
case DData: v = doUpdateCache(x, name, v);
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
values.add(v);
|
||||||
|
} else if( !f.isNull ) {
|
||||||
|
// if the field is not defined, give it a default value on insert
|
||||||
|
switch( f.t ) {
|
||||||
|
case DUInt, DTinyInt, DInt, DSingle, DFloat, DFlags(_), DBigInt, DTinyUInt, DSmallInt, DSmallUInt, DMediumInt, DMediumUInt, DEnum(_):
|
||||||
|
Reflect.setField(x, fieldName, 0);
|
||||||
|
case DBool:
|
||||||
|
Reflect.setField(x, fieldName, false);
|
||||||
|
case DTinyText, DText, DString(_), DSmallText, DSerialized:
|
||||||
|
Reflect.setField(x, fieldName, "");
|
||||||
|
case DSmallBinary, DNekoSerialized, DLongBinary, DBytes(_), DBinary:
|
||||||
|
Reflect.setField(x, fieldName, haxe.io.Bytes.alloc(0));
|
||||||
|
case DDate, DDateTime, DTimeStamp:
|
||||||
|
// default date might depend on database
|
||||||
|
case DId, DUId, DBigId, DNull, DInterval, DEncoded, DData:
|
||||||
|
// no default value for these
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Reflect.setField(cache, name, v);
|
||||||
|
}
|
||||||
|
s.add("INSERT INTO ");
|
||||||
|
s.add(table_name);
|
||||||
|
if (fields.length > 0 || cnx.dbName() != "SQLite")
|
||||||
|
{
|
||||||
|
s.add(" (");
|
||||||
|
s.add(fields.join(","));
|
||||||
|
s.add(") VALUES (");
|
||||||
|
var first = true;
|
||||||
|
for( v in values ) {
|
||||||
|
if( first )
|
||||||
|
first = false;
|
||||||
|
else
|
||||||
|
s.add(", ");
|
||||||
|
getCnx().addValue(s,v);
|
||||||
|
}
|
||||||
|
s.add(")");
|
||||||
|
} else {
|
||||||
|
s.add(" DEFAULT VALUES");
|
||||||
|
}
|
||||||
|
unsafeExecute(s.toString());
|
||||||
|
untyped x._lock = true;
|
||||||
|
// table with one key not defined : suppose autoincrement
|
||||||
|
if( table_keys.length == 1 && Reflect.field(x,table_keys[0]) == null )
|
||||||
|
Reflect.setField(x,table_keys[0],getCnx().lastInsertId());
|
||||||
|
addToCache(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline function isBinary( t : RecordInfos.RecordType ) {
|
||||||
|
return switch( t ) {
|
||||||
|
case DSmallBinary, DNekoSerialized, DLongBinary, DBytes(_), DBinary: true;
|
||||||
|
//case DData: true // -- disabled for implementation purposes
|
||||||
|
default: false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline function hasBinaryChanged( a : haxe.io.Bytes, b : haxe.io.Bytes ) {
|
||||||
|
return a != b && (a == null || b == null || a.compare(b) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function doUpdate( x : T ) {
|
||||||
|
if( untyped !x._lock )
|
||||||
|
throw "Cannot update a not locked object";
|
||||||
|
var upd = getUpdateStatement(x);
|
||||||
|
if (upd == null) return;
|
||||||
|
unsafeExecute(upd);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUpdateStatement( x : T ):Null<String>
|
||||||
|
{
|
||||||
|
unmake(x);
|
||||||
|
var s = new StringBuf();
|
||||||
|
s.add("UPDATE ");
|
||||||
|
s.add(table_name);
|
||||||
|
s.add(" SET ");
|
||||||
|
var cache = Reflect.field(x,cache_field);
|
||||||
|
var mod = false;
|
||||||
|
for( f in table_infos.fields ) {
|
||||||
|
if (table_keys.indexOf(f.name) >= 0)
|
||||||
|
continue;
|
||||||
|
var name = f.name,
|
||||||
|
fieldName = getFieldName(f);
|
||||||
|
var v : Dynamic = Reflect.field(x,fieldName);
|
||||||
|
var vc : Dynamic = Reflect.field(cache,name);
|
||||||
|
if( cache == null || v != vc ) {
|
||||||
|
switch( f.t ) {
|
||||||
|
case DSmallBinary, DNekoSerialized, DLongBinary, DBytes(_), DBinary:
|
||||||
|
if ( !hasBinaryChanged(v,vc) )
|
||||||
|
continue;
|
||||||
|
case DData:
|
||||||
|
v = doUpdateCache(x, name, v);
|
||||||
|
if( !hasBinaryChanged(v,vc) )
|
||||||
|
continue;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
if( mod )
|
||||||
|
s.add(", ");
|
||||||
|
else
|
||||||
|
mod = true;
|
||||||
|
s.add(quoteField(name));
|
||||||
|
s.add(" = ");
|
||||||
|
getCnx().addValue(s,v);
|
||||||
|
if ( cache != null )
|
||||||
|
Reflect.setField(cache,name,v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if( !mod )
|
||||||
|
return null;
|
||||||
|
s.add(" WHERE ");
|
||||||
|
addKeys(s,x);
|
||||||
|
return s.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function doDelete( x : T ) {
|
||||||
|
var s = new StringBuf();
|
||||||
|
s.add("DELETE FROM ");
|
||||||
|
s.add(table_name);
|
||||||
|
s.add(" WHERE ");
|
||||||
|
addKeys(s,x);
|
||||||
|
unsafeExecute(s.toString());
|
||||||
|
removeFromCache(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
function doLock( i : T ) {
|
||||||
|
if( untyped i._lock )
|
||||||
|
return;
|
||||||
|
var s = new StringBuf();
|
||||||
|
s.add("SELECT * FROM ");
|
||||||
|
s.add(table_name);
|
||||||
|
s.add(" WHERE ");
|
||||||
|
addKeys(s, i);
|
||||||
|
// will force sync
|
||||||
|
if( unsafeObject(s.toString(),true) != i )
|
||||||
|
throw "Could not lock object (was deleted ?); try restarting transaction";
|
||||||
|
}
|
||||||
|
|
||||||
|
function objectToString( it : T ) : String {
|
||||||
|
var s = new StringBuf();
|
||||||
|
s.add(table_name);
|
||||||
|
if( table_keys.length == 1 ) {
|
||||||
|
s.add("#");
|
||||||
|
s.add(Reflect.field(it,table_keys[0]));
|
||||||
|
} else {
|
||||||
|
s.add("(");
|
||||||
|
var first = true;
|
||||||
|
for( f in table_keys ) {
|
||||||
|
if( first )
|
||||||
|
first = false;
|
||||||
|
else
|
||||||
|
s.add(",");
|
||||||
|
s.add(quoteField(f));
|
||||||
|
s.add(":");
|
||||||
|
s.add(Reflect.field(it,f));
|
||||||
|
}
|
||||||
|
s.add(")");
|
||||||
|
}
|
||||||
|
return s.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function doSerialize( field : String, v : Dynamic ) : haxe.io.Bytes {
|
||||||
|
var s = new haxe.Serializer();
|
||||||
|
s.useEnumIndex = true;
|
||||||
|
s.serialize(v);
|
||||||
|
var str = s.toString();
|
||||||
|
#if neko
|
||||||
|
return neko.Lib.bytesReference(str);
|
||||||
|
#else
|
||||||
|
return haxe.io.Bytes.ofString(str);
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
|
function doUnserialize( field : String, b : haxe.io.Bytes ) : Dynamic {
|
||||||
|
if( b == null )
|
||||||
|
return null;
|
||||||
|
var str;
|
||||||
|
#if neko
|
||||||
|
str = neko.Lib.stringReference(b);
|
||||||
|
#else
|
||||||
|
str = b.toString();
|
||||||
|
#end
|
||||||
|
if( str == "" )
|
||||||
|
return null;
|
||||||
|
return haxe.Unserializer.run(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------- INTERNAL API -------------------------- */
|
||||||
|
|
||||||
|
function normalizeCache(x:CacheType<T>)
|
||||||
|
{
|
||||||
|
for (f in Reflect.fields(x) )
|
||||||
|
{
|
||||||
|
var val:Dynamic = Reflect.field(x,f), info = table_infos.hfields.get(f);
|
||||||
|
if (info != null)
|
||||||
|
{
|
||||||
|
if (val != null) switch (info.t) {
|
||||||
|
case DDate, DDateTime if (!Std.is(val,Date)):
|
||||||
|
if (Std.is(val,Float))
|
||||||
|
{
|
||||||
|
val = Date.fromTime(val);
|
||||||
|
} else {
|
||||||
|
var v = val + "";
|
||||||
|
var index = v.indexOf('.');
|
||||||
|
if (index >= 0)
|
||||||
|
v = v.substr(0,index);
|
||||||
|
val = Date.fromString(v);
|
||||||
|
}
|
||||||
|
case DSmallBinary, DLongBinary, DBinary, DBytes(_), DData if (Std.is(val, String)):
|
||||||
|
val = haxe.io.Bytes.ofString(val);
|
||||||
|
case DString(_) | DTinyText | DSmallText | DText if(!Std.is(val,String)):
|
||||||
|
val = val + "";
|
||||||
|
#if (cs && erase_generics)
|
||||||
|
// on C#, SQLite Ints are returned as Int64
|
||||||
|
case DInt if (!Std.is(val,Int)):
|
||||||
|
val = cast(val,Int);
|
||||||
|
#end
|
||||||
|
case DBool if (!Std.is(val,Bool)):
|
||||||
|
if (Std.is(val,Int))
|
||||||
|
val = val != 0;
|
||||||
|
else if (Std.is(val, String)) switch (val.toLowerCase()) {
|
||||||
|
case "1", "true": val = true;
|
||||||
|
case "0", "false": val = false;
|
||||||
|
}
|
||||||
|
case DFloat if (Std.is(val,String)):
|
||||||
|
val = Std.parseFloat(val);
|
||||||
|
case _:
|
||||||
|
}
|
||||||
|
Reflect.setField(x, f, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
function make( x : T ) {
|
||||||
|
}
|
||||||
|
|
||||||
|
function unmake( x : T ) {
|
||||||
|
}
|
||||||
|
|
||||||
|
function quoteField(f : String) {
|
||||||
|
return KEYWORDS.exists(f.toLowerCase()) ? "`"+f+"`" : f;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addKeys( s : StringBuf, x : {} ) {
|
||||||
|
var first = true;
|
||||||
|
for( k in table_keys ) {
|
||||||
|
if( first )
|
||||||
|
first = false;
|
||||||
|
else
|
||||||
|
s.add(" AND ");
|
||||||
|
s.add(quoteField(k));
|
||||||
|
s.add(" = ");
|
||||||
|
var f = Reflect.field(x,k);
|
||||||
|
if( f == null )
|
||||||
|
throw ("Missing key "+k);
|
||||||
|
getCnx().addValue(s,f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function unsafeExecute( sql : String ) {
|
||||||
|
return getCnx().request(sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function unsafeObject( sql : String, lock : Bool ) : T {
|
||||||
|
if( lock != false ) {
|
||||||
|
lock = true;
|
||||||
|
sql += getLockMode();
|
||||||
|
}
|
||||||
|
var r = unsafeExecute(sql);
|
||||||
|
var r = r.hasNext() ? r.next() : null;
|
||||||
|
if( r == null )
|
||||||
|
return null;
|
||||||
|
normalizeCache(r);
|
||||||
|
var c = getFromCache(r,lock);
|
||||||
|
if( c != null )
|
||||||
|
return c;
|
||||||
|
r = cacheObject(r,lock);
|
||||||
|
make(r);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function unsafeObjects( sql : String, lock : Bool ) : List<T> {
|
||||||
|
if( lock != false ) {
|
||||||
|
lock = true;
|
||||||
|
sql += getLockMode();
|
||||||
|
}
|
||||||
|
var l = unsafeExecute(sql).results();
|
||||||
|
var l2 = new List<T>();
|
||||||
|
for( x in l ) {
|
||||||
|
normalizeCache(x);
|
||||||
|
var c = getFromCache(x,lock);
|
||||||
|
if( c != null )
|
||||||
|
l2.add(c);
|
||||||
|
else {
|
||||||
|
x = cacheObject(x,lock);
|
||||||
|
make(x);
|
||||||
|
l2.add(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return l2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function unsafeCount( sql : String ) {
|
||||||
|
return unsafeExecute(sql).getIntResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function unsafeDelete( sql : String ) {
|
||||||
|
unsafeExecute(sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function unsafeGet( id : Dynamic, ?lock : Bool ) : T {
|
||||||
|
if( lock == null ) lock = true;
|
||||||
|
if( table_keys.length != 1 )
|
||||||
|
throw "Invalid number of keys";
|
||||||
|
if( id == null )
|
||||||
|
return null;
|
||||||
|
var x : Dynamic = getFromCacheKey(Std.string(id) + table_name);
|
||||||
|
if( x != null && (!lock || x._lock) )
|
||||||
|
return x;
|
||||||
|
var s = new StringBuf();
|
||||||
|
s.add("SELECT * FROM ");
|
||||||
|
s.add(table_name);
|
||||||
|
s.add(" WHERE ");
|
||||||
|
s.add(quoteField(table_keys[0]));
|
||||||
|
s.add(" = ");
|
||||||
|
getCnx().addValue(s,id);
|
||||||
|
return unsafeObject(s.toString(), lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function unsafeGetWithKeys( keys : { }, ?lock : Bool ) : T {
|
||||||
|
if( lock == null ) lock = true;
|
||||||
|
var x : Dynamic = getFromCacheKey(makeCacheKey(cast keys));
|
||||||
|
if( x != null && (!lock || x._lock) )
|
||||||
|
return x;
|
||||||
|
var s = new StringBuf();
|
||||||
|
s.add("SELECT * FROM ");
|
||||||
|
s.add(table_name);
|
||||||
|
s.add(" WHERE ");
|
||||||
|
addKeys(s,keys);
|
||||||
|
return unsafeObject(s.toString(),lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function unsafeGetId( o : T ) : Dynamic {
|
||||||
|
return o == null ? null : Reflect.field(o, table_keys[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function nullCompare( a : String, b : String, eq : Bool ) {
|
||||||
|
if (a == null || a == 'NULL') {
|
||||||
|
return eq ? '$b IS NULL' : '$b IS NOT NULL';
|
||||||
|
} else if (b == null || b == 'NULL') {
|
||||||
|
return eq ? '$a IS NULL' : '$a IS NOT NULL';
|
||||||
|
}
|
||||||
|
// we can't use a null-safe operator here
|
||||||
|
if( cnx.dbName() != "MySQL" )
|
||||||
|
return a + (eq ? " = " : " != ") + b;
|
||||||
|
var sql = a+" <=> "+b;
|
||||||
|
if( !eq ) sql = "NOT("+sql+")";
|
||||||
|
return sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addCondition(s : StringBuf,x) {
|
||||||
|
var first = true;
|
||||||
|
if( x != null )
|
||||||
|
for( f in Reflect.fields(x) ) {
|
||||||
|
if( first )
|
||||||
|
first = false;
|
||||||
|
else
|
||||||
|
s.add(" AND ");
|
||||||
|
s.add(quoteField(f));
|
||||||
|
var d = Reflect.field(x,f);
|
||||||
|
if( d == null )
|
||||||
|
s.add(" IS NULL");
|
||||||
|
else {
|
||||||
|
s.add(" = ");
|
||||||
|
getCnx().addValue(s,d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if( first )
|
||||||
|
s.add("TRUE");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------- MISC API ------------------------------ */
|
||||||
|
|
||||||
|
public function dbClass() : Class<Dynamic> {
|
||||||
|
return cast class_proto;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function dbInfos() {
|
||||||
|
return table_infos;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCnx() {
|
||||||
|
return cnx;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLockMode() {
|
||||||
|
return lockMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Remove the cached value for the given Object field : this will ensure
|
||||||
|
that the value is updated when calling .update(). This is necessary if
|
||||||
|
you are modifying binary data in-place since the cache will be modified
|
||||||
|
as well.
|
||||||
|
**/
|
||||||
|
public function forceUpdate( o : T, field : String ) {
|
||||||
|
// set a reference that will ensure != and .compare() != 0
|
||||||
|
Reflect.setField(Reflect.field(o,cache_field),field,null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------- INIT / CLEANUP ------------------------- */
|
||||||
|
|
||||||
|
public static function initialize() {
|
||||||
|
var l = init_list;
|
||||||
|
init_list = new List();
|
||||||
|
for( m in l )
|
||||||
|
for( r in m.table_infos.relations )
|
||||||
|
m.initRelation(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function cleanup() {
|
||||||
|
object_cache = new haxe.ds.StringMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
function initRelation( r : RecordInfos.RecordRelation ) {
|
||||||
|
// setup getter/setter
|
||||||
|
var spod : Dynamic = Type.resolveClass(r.type);
|
||||||
|
if( spod == null ) throw "Missing spod type " + r.type;
|
||||||
|
var manager : Manager<Dynamic> = spod.manager;
|
||||||
|
var hprop = "__"+r.prop;
|
||||||
|
var hkey = r.key;
|
||||||
|
var lock = r.lock;
|
||||||
|
if( manager == null || manager.table_keys == null ) throw ("Invalid manager for relation "+table_name+":"+r.prop);
|
||||||
|
if( manager.table_keys.length != 1 ) throw ("Relation " + r.prop + "(" + r.key + ") on a multiple key table");
|
||||||
|
}
|
||||||
|
|
||||||
|
function __get( x : Dynamic, prop : String, key : String, lock ) {
|
||||||
|
var v = Reflect.field(x,prop);
|
||||||
|
if( v != null )
|
||||||
|
return v;
|
||||||
|
var y = unsafeGet(Reflect.field(x, key), lock);
|
||||||
|
Reflect.setField(x,prop,v);
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
|
function __set( x : Dynamic, prop : String, key : String, v : T ) {
|
||||||
|
Reflect.setField(x,prop,v);
|
||||||
|
if( v == null )
|
||||||
|
Reflect.setField(x,key,null);
|
||||||
|
else
|
||||||
|
Reflect.setField(x,key,Reflect.field(v,table_keys[0]));
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------- OBJECT CACHE -------------------------- */
|
||||||
|
|
||||||
|
function makeCacheKey( x : T ) : String {
|
||||||
|
if( table_keys.length == 1 ) {
|
||||||
|
var k = Reflect.field(x,table_keys[0]);
|
||||||
|
if( k == null )
|
||||||
|
throw("Missing key "+table_keys[0]);
|
||||||
|
return Std.string(k)+table_name;
|
||||||
|
}
|
||||||
|
var s = new StringBuf();
|
||||||
|
for( k in table_keys ) {
|
||||||
|
var v = Reflect.field(x,k);
|
||||||
|
if( k == null )
|
||||||
|
throw("Missing key "+k);
|
||||||
|
s.add(v);
|
||||||
|
s.add("#");
|
||||||
|
}
|
||||||
|
s.add(table_name);
|
||||||
|
return s.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function addToCache( x : CacheType<T> ) {
|
||||||
|
object_cache.set(makeCacheKey(x),x);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeFromCache( x : CacheType<T> ) {
|
||||||
|
object_cache.remove(makeCacheKey(x));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFromCacheKey( key : String ) : T {
|
||||||
|
return cast object_cache.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFromCache( x : CacheType<T>, lock : Bool ) : T {
|
||||||
|
var c : Dynamic = object_cache.get(makeCacheKey(x));
|
||||||
|
if( c != null && lock && !c._lock ) {
|
||||||
|
// synchronize the fields since our result is up-to-date !
|
||||||
|
for( f in Reflect.fields(c) )
|
||||||
|
Reflect.deleteField(c,f);
|
||||||
|
for (f in table_infos.fields)
|
||||||
|
{
|
||||||
|
var name = f.name,
|
||||||
|
fieldName = getFieldName(f);
|
||||||
|
Reflect.setField(c,fieldName,Reflect.field(x,name));
|
||||||
|
}
|
||||||
|
// mark as locked
|
||||||
|
c._lock = true;
|
||||||
|
// restore our manager
|
||||||
|
#if !neko
|
||||||
|
untyped c._manager = this;
|
||||||
|
#end
|
||||||
|
// use the new object as our cache of fields
|
||||||
|
Reflect.setField(c,cache_field,x);
|
||||||
|
// remake object
|
||||||
|
make(c);
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------- QUOTES -------------------------- */
|
||||||
|
|
||||||
|
public static function quoteAny( v : Dynamic ) {
|
||||||
|
if (v == null) {
|
||||||
|
return 'NULL';
|
||||||
|
}
|
||||||
|
|
||||||
|
var s = new StringBuf();
|
||||||
|
cnx.addValue(s, v);
|
||||||
|
return s.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function quoteList( v : String, it : Iterable<Dynamic> ) {
|
||||||
|
var b = new StringBuf();
|
||||||
|
var first = true;
|
||||||
|
if( it != null )
|
||||||
|
for( v in it ) {
|
||||||
|
if( first ) first = false else b.addChar(','.code);
|
||||||
|
cnx.addValue(b, v);
|
||||||
|
}
|
||||||
|
if( first )
|
||||||
|
return "FALSE";
|
||||||
|
return v + " IN (" + b.toString() + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need Bytes.toString to not be DCE'd. See #1937
|
||||||
|
@:keep static function __depends() { return haxe.io.Bytes.alloc(0).toString(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private typedef CacheType<T> = Dynamic;
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C)2005-2016 Haxe Foundation
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
* copy of this software and associated documentation files (the "Software"),
|
||||||
|
* to deal in the Software without restriction, including without limitation
|
||||||
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
* and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
* Software is furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
* DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package sys.db;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Record Object : the persistent object base type. See the tutorial on Haxe
|
||||||
|
website to learn how to use Record.
|
||||||
|
**/
|
||||||
|
@:keepSub
|
||||||
|
@:autoBuild(sys.db.RecordMacros.macroBuild()) @:skipFields
|
||||||
|
class Object {
|
||||||
|
|
||||||
|
var _lock(default,never) : Bool;
|
||||||
|
var _manager(default,never) : sys.db.Manager<Dynamic>;
|
||||||
|
#if !neko
|
||||||
|
@:keep var __cache__:Dynamic;
|
||||||
|
#end
|
||||||
|
|
||||||
|
public function new() {
|
||||||
|
#if !neko
|
||||||
|
if( _manager == null ) untyped _manager = __getManager();
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !neko
|
||||||
|
private function __getManager():sys.db.Manager<Dynamic>
|
||||||
|
{
|
||||||
|
var cls:Dynamic = Type.getClass(this);
|
||||||
|
return cls.manager;
|
||||||
|
}
|
||||||
|
#end
|
||||||
|
|
||||||
|
public function insert() {
|
||||||
|
untyped _manager.doInsert(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update() {
|
||||||
|
untyped _manager.doUpdate(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function lock() {
|
||||||
|
untyped _manager.doLock(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete() {
|
||||||
|
untyped _manager.doDelete(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isLocked() {
|
||||||
|
return _lock;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toString() : String {
|
||||||
|
return untyped _manager.objectToString(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C)2005-2016 Haxe Foundation
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
* copy of this software and associated documentation files (the "Software"),
|
||||||
|
* to deal in the Software without restriction, including without limitation
|
||||||
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
* and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
* Software is furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
* DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package sys.db;
|
||||||
|
|
||||||
|
enum RecordType {
|
||||||
|
DId;
|
||||||
|
DInt;
|
||||||
|
DUId;
|
||||||
|
DUInt;
|
||||||
|
DBigId;
|
||||||
|
DBigInt;
|
||||||
|
DSingle;
|
||||||
|
DFloat;
|
||||||
|
DBool;
|
||||||
|
DString( n : Int );
|
||||||
|
DDate;
|
||||||
|
DDateTime;
|
||||||
|
DTimeStamp;
|
||||||
|
DTinyText;
|
||||||
|
DSmallText;
|
||||||
|
DText;
|
||||||
|
DSmallBinary;
|
||||||
|
DLongBinary;
|
||||||
|
DBinary;
|
||||||
|
DBytes( n : Int );
|
||||||
|
DEncoded;
|
||||||
|
DSerialized;
|
||||||
|
DNekoSerialized;
|
||||||
|
DFlags( flags : Array<String>, autoSize : Bool );
|
||||||
|
DTinyInt;
|
||||||
|
DTinyUInt;
|
||||||
|
DSmallInt;
|
||||||
|
DSmallUInt;
|
||||||
|
DMediumInt;
|
||||||
|
DMediumUInt;
|
||||||
|
DData;
|
||||||
|
DEnum( name : String );
|
||||||
|
// specific for intermediate calculus
|
||||||
|
DInterval;
|
||||||
|
DNull;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef RecordField = {
|
||||||
|
var name : String;
|
||||||
|
var t : RecordType;
|
||||||
|
var isNull : Bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef RecordRelation = {
|
||||||
|
var prop : String;
|
||||||
|
var key : String;
|
||||||
|
var type : String;
|
||||||
|
var module : String;
|
||||||
|
var cascade : Bool;
|
||||||
|
var lock : Bool;
|
||||||
|
var isNull : Bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef RecordInfos = {
|
||||||
|
var name : String;
|
||||||
|
var key : Array<String>;
|
||||||
|
var fields : Array<RecordField>;
|
||||||
|
var hfields : Map<String,RecordField>;
|
||||||
|
var relations : Array<RecordRelation>;
|
||||||
|
var indexes : Array<{ keys : Array<String>, unique : Bool }>;
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C)2005-2016 Haxe Foundation
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
* copy of this software and associated documentation files (the "Software"),
|
||||||
|
* to deal in the Software without restriction, including without limitation
|
||||||
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
* and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
* Software is furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
* DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package sys.db;
|
||||||
|
import sys.db.RecordInfos;
|
||||||
|
|
||||||
|
class TableCreate {
|
||||||
|
|
||||||
|
static function autoInc( dbName ) {
|
||||||
|
// on SQLite, autoincrement is necessary to be primary key as well
|
||||||
|
return dbName == "SQLite" ? "PRIMARY KEY AUTOINCREMENT" : "AUTO_INCREMENT";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getTypeSQL( t : RecordType, dbName : String ) {
|
||||||
|
return switch( t ) {
|
||||||
|
case DId: "INTEGER "+autoInc(dbName);
|
||||||
|
case DUId: "INTEGER UNSIGNED "+autoInc(dbName);
|
||||||
|
case DInt, DEncoded: "INTEGER";
|
||||||
|
case DUInt: "INTEGER UNSIGNED";
|
||||||
|
case DTinyInt: "TINYINT";
|
||||||
|
case DTinyUInt, DEnum(_): "TINYINT UNSIGNED";
|
||||||
|
case DSmallInt: "SMALLINT";
|
||||||
|
case DSmallUInt: "SMALLINT UNSIGNED";
|
||||||
|
case DMediumInt: "MEDIUMINT";
|
||||||
|
case DMediumUInt: "MEDIUMINT UNSIGNED";
|
||||||
|
case DSingle: "FLOAT";
|
||||||
|
case DFloat: "DOUBLE";
|
||||||
|
case DBool: "TINYINT(1)";
|
||||||
|
case DString(n): "VARCHAR("+n+")";
|
||||||
|
case DDate: "DATE";
|
||||||
|
case DDateTime: "DATETIME";
|
||||||
|
case DTimeStamp: "TIMESTAMP DEFAULT 0";
|
||||||
|
case DTinyText: "TINYTEXT";
|
||||||
|
case DSmallText: "TEXT";
|
||||||
|
case DText, DSerialized: "MEDIUMTEXT";
|
||||||
|
case DSmallBinary: "BLOB";
|
||||||
|
case DBinary, DNekoSerialized, DData: "MEDIUMBLOB";
|
||||||
|
case DLongBinary: "LONGBLOB";
|
||||||
|
case DBigInt: "BIGINT";
|
||||||
|
case DBigId: "BIGINT "+autoInc(dbName);
|
||||||
|
case DBytes(n): "BINARY(" + n + ")";
|
||||||
|
case DFlags(fl, auto): getTypeSQL(auto ? (fl.length <= 8 ? DTinyUInt : (fl.length <= 16 ? DSmallUInt : (fl.length <= 24 ? DMediumUInt : DInt))) : DInt, dbName);
|
||||||
|
case DNull, DInterval: throw "assert";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function create( manager : sys.db.Manager<Dynamic>, ?engine ) {
|
||||||
|
function quote(v:String):String {
|
||||||
|
return untyped manager.quoteField(v);
|
||||||
|
}
|
||||||
|
var cnx : Connection = untyped manager.getCnx();
|
||||||
|
if( cnx == null )
|
||||||
|
throw "SQL Connection not initialized on Manager";
|
||||||
|
var dbName = cnx.dbName();
|
||||||
|
var infos = manager.dbInfos();
|
||||||
|
var sql = "CREATE TABLE " + quote(infos.name) + " (";
|
||||||
|
var decls = [];
|
||||||
|
var hasID = false;
|
||||||
|
for( f in infos.fields ) {
|
||||||
|
switch( f.t ) {
|
||||||
|
case DId:
|
||||||
|
hasID = true;
|
||||||
|
case DUId, DBigId:
|
||||||
|
hasID = true;
|
||||||
|
if( dbName == "SQLite" )
|
||||||
|
throw "S" + Std.string(f.t).substr(1)+" is not supported by " + dbName + " : use SId instead";
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
decls.push(quote(f.name)+" "+getTypeSQL(f.t,dbName)+(f.isNull ? "" : " NOT NULL"));
|
||||||
|
}
|
||||||
|
if( dbName != "SQLite" || !hasID )
|
||||||
|
decls.push("PRIMARY KEY ("+Lambda.map(infos.key,quote).join(",")+")");
|
||||||
|
sql += decls.join(",");
|
||||||
|
sql += ")";
|
||||||
|
if( engine != null )
|
||||||
|
sql += "ENGINE="+engine;
|
||||||
|
cnx.request(sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function exists( manager : sys.db.Manager<Dynamic> ) : Bool {
|
||||||
|
var cnx : Connection = untyped manager.getCnx();
|
||||||
|
if( cnx == null )
|
||||||
|
throw "SQL Connection not initialized on Manager";
|
||||||
|
try {
|
||||||
|
cnx.request("SELECT * FROM `"+manager.dbInfos().name+"` LIMIT 1");
|
||||||
|
return true;
|
||||||
|
} catch( e : Dynamic ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C)2005-2017 Haxe Foundation
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
* copy of this software and associated documentation files (the "Software"),
|
||||||
|
* to deal in the Software without restriction, including without limitation
|
||||||
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
* and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
* Software is furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
* DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package sys.db;
|
||||||
|
|
||||||
|
class Transaction {
|
||||||
|
|
||||||
|
public static function isDeadlock(e : Dynamic) {
|
||||||
|
return Std.is(e,String) && ~/try restarting transaction/.match(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function runMainLoop(mainFun,logError,count) {
|
||||||
|
try {
|
||||||
|
mainFun();
|
||||||
|
} catch( e : Dynamic ) {
|
||||||
|
if( count > 0 && isDeadlock(e) ) {
|
||||||
|
Manager.cleanup();
|
||||||
|
Manager.cnx.rollback(); // should be already done, but in case...
|
||||||
|
Manager.cnx.startTransaction();
|
||||||
|
runMainLoop(mainFun,logError,count-1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if( logError == null ) {
|
||||||
|
Manager.cnx.rollback();
|
||||||
|
#if neko
|
||||||
|
neko.Lib.rethrow(e);
|
||||||
|
#else
|
||||||
|
throw e;
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
logError(e); // should ROLLBACK if needed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function main( cnx, mainFun : Void -> Void, ?logError : Dynamic -> Void ) {
|
||||||
|
Manager.initialize();
|
||||||
|
Manager.cnx = cnx;
|
||||||
|
Manager.cnx.startTransaction();
|
||||||
|
runMainLoop(mainFun,logError,3);
|
||||||
|
try {
|
||||||
|
Manager.cnx.commit();
|
||||||
|
} catch( e : String ) {
|
||||||
|
// sqlite can have errors on commit
|
||||||
|
if( ~/Database is busy/.match(e) )
|
||||||
|
logError(e);
|
||||||
|
}
|
||||||
|
Manager.cnx.close();
|
||||||
|
Manager.cnx = null;
|
||||||
|
Manager.cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C)2005-2016 Haxe Foundation
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
* copy of this software and associated documentation files (the "Software"),
|
||||||
|
* to deal in the Software without restriction, including without limitation
|
||||||
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
* and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
* Software is furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
* DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package sys.db;
|
||||||
|
|
||||||
|
// basic types
|
||||||
|
|
||||||
|
/** int with auto increment **/
|
||||||
|
@:noPackageRestrict
|
||||||
|
typedef SId = Null<Int>
|
||||||
|
|
||||||
|
/** int unsigned with auto increment **/
|
||||||
|
typedef SUId = Null<Int>
|
||||||
|
|
||||||
|
/** big int with auto increment **/
|
||||||
|
typedef SBigId = Null<Float>
|
||||||
|
|
||||||
|
typedef SInt = Null<Int>
|
||||||
|
|
||||||
|
typedef SUInt = Null<Int>
|
||||||
|
|
||||||
|
typedef SBigInt = Null<Float>
|
||||||
|
|
||||||
|
/** single precision float **/
|
||||||
|
typedef SSingle = Null<Float>
|
||||||
|
|
||||||
|
/** double precision float **/
|
||||||
|
typedef SFloat = Null<Float>
|
||||||
|
|
||||||
|
/** use `tinyint(1)` to distinguish with int **/
|
||||||
|
typedef SBool = Null<Bool>
|
||||||
|
|
||||||
|
/** same as `varchar(n)` **/
|
||||||
|
typedef SString<Const> = String
|
||||||
|
|
||||||
|
/** date only, use `SDateTime` for date+time **/
|
||||||
|
typedef SDate = Date
|
||||||
|
|
||||||
|
/** mysql DateTime **/
|
||||||
|
typedef SDateTime = Date
|
||||||
|
|
||||||
|
/** mysql Timestamp **/
|
||||||
|
typedef STimeStamp = Date
|
||||||
|
|
||||||
|
/** TinyText (up to 255 bytes) **/
|
||||||
|
typedef STinyText = String
|
||||||
|
|
||||||
|
/** Text (up to 64KB) **/
|
||||||
|
typedef SSmallText = String
|
||||||
|
|
||||||
|
/** MediumText (up to 24MB) **/
|
||||||
|
typedef SText = String
|
||||||
|
|
||||||
|
/** Blob type (up to 64KB) **/
|
||||||
|
typedef SSmallBinary = haxe.io.Bytes
|
||||||
|
|
||||||
|
/** LongBlob type (up to 4GB) **/
|
||||||
|
typedef SLongBinary = haxe.io.Bytes
|
||||||
|
|
||||||
|
/** MediumBlob type (up to 24MB) **/
|
||||||
|
typedef SBinary = haxe.io.Bytes
|
||||||
|
|
||||||
|
/** same as binary(n) **/
|
||||||
|
typedef SBytes<Const> = haxe.io.Bytes
|
||||||
|
|
||||||
|
/** one byte signed `-128...127` **/
|
||||||
|
typedef STinyInt = Null<Int>
|
||||||
|
|
||||||
|
/** two bytes signed `-32768...32767` **/
|
||||||
|
typedef SSmallInt = Null<Int>
|
||||||
|
|
||||||
|
/** three bytes signed `-8388608...8388607` **/
|
||||||
|
typedef SMediumInt = Null<Int>
|
||||||
|
|
||||||
|
/** one byte `0...255` **/
|
||||||
|
typedef STinyUInt = Null<Int>
|
||||||
|
|
||||||
|
/** two bytes `0...65535` **/
|
||||||
|
typedef SSmallUInt = Null<Int>
|
||||||
|
|
||||||
|
/** three bytes `0...16777215` **/
|
||||||
|
typedef SMediumUInt = Null<Int>
|
||||||
|
|
||||||
|
// extra
|
||||||
|
|
||||||
|
/** specify that this field is nullable **/
|
||||||
|
typedef SNull<T> = Null<T>
|
||||||
|
|
||||||
|
/** specify that the integer use custom encoding **/
|
||||||
|
typedef SEncoded = Null<Int>
|
||||||
|
|
||||||
|
/** Haxe Serialized string **/
|
||||||
|
typedef SSerialized = String
|
||||||
|
|
||||||
|
/** native neko serialized bytes **/
|
||||||
|
typedef SNekoSerialized = haxe.io.Bytes
|
||||||
|
|
||||||
|
/** a set of bitflags of different enum values **/
|
||||||
|
typedef SFlags<T:EnumValue> = Null<haxe.EnumFlags<T>>
|
||||||
|
|
||||||
|
/** same as `SFlags` but will adapt the storage size to the number of flags **/
|
||||||
|
typedef SSmallFlags<T:EnumValue> = SFlags<T>;
|
||||||
|
|
||||||
|
/** allow to store any value in serialized form **/
|
||||||
|
typedef SData<T> = Null<T>
|
||||||
|
|
||||||
|
/** allow to store an enum value that does not have parameters as a simple int **/
|
||||||
|
typedef SEnum<E:EnumValue> = Null<E>
|
||||||
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import hex.unittest.notifier.*;
|
||||||
|
import hex.unittest.runner.*;
|
||||||
|
|
||||||
|
using Lambda;
|
||||||
|
|
||||||
|
class Main
|
||||||
|
{
|
||||||
|
static function main() {
|
||||||
|
var arg = Sys.args()[0];
|
||||||
|
var mysqlConnection = (arg != null && arg.substr(0,8)=="mysql://") ? arg : null;
|
||||||
|
|
||||||
|
var emu = new ExMachinaUnitCore();
|
||||||
|
emu.addListener(new ConsoleNotifier(false));
|
||||||
|
emu.addListener(new ExitingNotifier());
|
||||||
|
if(mysqlConnection!=null) emu.addTest(MySQLTest);
|
||||||
|
emu.addTest(SQLiteTest);
|
||||||
|
emu.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,588 @@
|
|||||||
|
import sys.db.*;
|
||||||
|
import sys.db.Types;
|
||||||
|
import haxe.io.Bytes;
|
||||||
|
import haxe.EnumFlags;
|
||||||
|
import MySpodClass;
|
||||||
|
import hex.unittest.assertion.Assert;
|
||||||
|
import hex.unittest.notifier.*;
|
||||||
|
import hex.unittest.runner.*;
|
||||||
|
|
||||||
|
using Lambda;
|
||||||
|
|
||||||
|
class MySQLTest
|
||||||
|
{
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public function before()
|
||||||
|
{
|
||||||
|
connectDb();
|
||||||
|
|
||||||
|
try Manager.cnx.request('DROP TABLE MySpodClass') catch(e:Dynamic) {}
|
||||||
|
try Manager.cnx.request('DROP TABLE OtherSpodClass') catch(e:Dynamic) {}
|
||||||
|
try Manager.cnx.request('DROP TABLE NullableSpodClass') catch(e:Dynamic) {}
|
||||||
|
try Manager.cnx.request('DROP TABLE ClassWithStringId') catch(e:Dynamic) {}
|
||||||
|
try Manager.cnx.request('DROP TABLE ClassWithStringIdRef') catch(e:Dynamic) {}
|
||||||
|
try Manager.cnx.request('DROP TABLE IssueC3828') catch(e:Dynamic) {}
|
||||||
|
try Manager.cnx.request('DROP TABLE Issue6041Table') catch(e:Dynamic) {}
|
||||||
|
TableCreate.create(MySpodClass.manager);
|
||||||
|
TableCreate.create(OtherSpodClass.manager);
|
||||||
|
TableCreate.create(NullableSpodClass.manager);
|
||||||
|
TableCreate.create(ClassWithStringId.manager);
|
||||||
|
TableCreate.create(ClassWithStringIdRef.manager);
|
||||||
|
TableCreate.create(IssueC3828.manager);
|
||||||
|
TableCreate.create(Issue6041Table.manager);
|
||||||
|
|
||||||
|
Manager.cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public function after()
|
||||||
|
{
|
||||||
|
Manager.cnx.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
function connectDb() {
|
||||||
|
var dbstr = Sys.args()[0];
|
||||||
|
var dbreg = ~/([^:]+):\/\/([^:]+):([^@]*?)@([^:]+)(:[0-9]+)?\/(.*?)$/;
|
||||||
|
if( !dbreg.match(dbstr) )
|
||||||
|
throw "Configuration requires a valid database attribute, format is : mysql://user:password@host:port/dbname";
|
||||||
|
var port = dbreg.matched(5);
|
||||||
|
var dbparams = {
|
||||||
|
user:dbreg.matched(2),
|
||||||
|
pass:dbreg.matched(3),
|
||||||
|
host:dbreg.matched(4),
|
||||||
|
port:port == null ? 3306 : Std.parseInt(port.substr(1)),
|
||||||
|
database:dbreg.matched(6),
|
||||||
|
socket:null
|
||||||
|
};
|
||||||
|
|
||||||
|
sys.db.Manager.cnx = sys.db.Mysql.connect(dbparams);
|
||||||
|
sys.db.Manager.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDefaultClass()
|
||||||
|
{
|
||||||
|
var scls = new MySpodClass();
|
||||||
|
scls.int = 1;
|
||||||
|
scls.double = 2.0;
|
||||||
|
scls.boolean = true;
|
||||||
|
scls.string = "some string";
|
||||||
|
scls.date = new Date(2012, 7, 30, 0, 0, 0);
|
||||||
|
scls.abstractType = "other string";
|
||||||
|
|
||||||
|
var bytes = Bytes.ofString("\x01\n\r'\x02");
|
||||||
|
scls.binary = bytes;
|
||||||
|
scls.enumFlags = EnumFlags.ofInt(0);
|
||||||
|
scls.enumFlags.set(FirstValue);
|
||||||
|
scls.enumFlags.set(ThirdValue);
|
||||||
|
scls.bytes = Bytes.ofString("\000a");
|
||||||
|
|
||||||
|
scls.data = [new ComplexClass( { name:"test", array:["this", "is", "a", "test"] } )];
|
||||||
|
scls.anEnum = SecondValue;
|
||||||
|
|
||||||
|
return scls;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDefaultNull() {
|
||||||
|
var scls = new NullableSpodClass();
|
||||||
|
scls.int = 1;
|
||||||
|
scls.double = 2.0;
|
||||||
|
scls.boolean = true;
|
||||||
|
scls.string = "some string";
|
||||||
|
scls.date = new Date(2012, 7, 30, 0, 0, 0);
|
||||||
|
scls.abstractType = "other string";
|
||||||
|
|
||||||
|
var bytes = Bytes.ofString("\x01\n\r'\x02");
|
||||||
|
scls.binary = bytes;
|
||||||
|
scls.enumFlags = EnumFlags.ofInt(0);
|
||||||
|
scls.enumFlags.set(FirstValue);
|
||||||
|
scls.enumFlags.set(ThirdValue);
|
||||||
|
|
||||||
|
scls.data = [new ComplexClass( { name:"test", array:["this", "is", "a", "test"] } )];
|
||||||
|
scls.anEnum = SecondValue;
|
||||||
|
return scls;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getNull<T>():Null<T> {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !php
|
||||||
|
//TODO : these tests fail with PHP 7 and haxe 3.4.7
|
||||||
|
@Test
|
||||||
|
public function testNull() {
|
||||||
|
var n1 = getDefaultNull();
|
||||||
|
n1.insert();
|
||||||
|
var n2 = new NullableSpodClass();
|
||||||
|
n2.insert();
|
||||||
|
var id = n2.theId;
|
||||||
|
|
||||||
|
n1 = null; n2 = null;
|
||||||
|
Manager.cleanup();
|
||||||
|
|
||||||
|
var nullVal = getNull();
|
||||||
|
inline function checkReq(lst:List<NullableSpodClass>, ?nres=1, ?pos:haxe.PosInfos) {
|
||||||
|
Assert.equals(nres, lst.length, null, pos);
|
||||||
|
if (lst.length == 1) {
|
||||||
|
Assert.equals(id, lst.first().theId, null, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkReq(NullableSpodClass.manager.search($relationNullable == null), 2);
|
||||||
|
checkReq(NullableSpodClass.manager.search($data == null));
|
||||||
|
checkReq(NullableSpodClass.manager.search($anEnum == null));
|
||||||
|
|
||||||
|
checkReq(NullableSpodClass.manager.search($int == null));
|
||||||
|
checkReq(NullableSpodClass.manager.search($double == null));
|
||||||
|
checkReq(NullableSpodClass.manager.search($boolean == null));
|
||||||
|
checkReq(NullableSpodClass.manager.search($string == null));
|
||||||
|
checkReq(NullableSpodClass.manager.search($date == null));
|
||||||
|
checkReq(NullableSpodClass.manager.search($binary == null));
|
||||||
|
checkReq(NullableSpodClass.manager.search($abstractType == null));
|
||||||
|
|
||||||
|
checkReq(NullableSpodClass.manager.search($enumFlags == null));
|
||||||
|
|
||||||
|
|
||||||
|
var relationNullable:Null<OtherSpodClass> = getNull();
|
||||||
|
checkReq(NullableSpodClass.manager.search($relationNullable == relationNullable), 2);
|
||||||
|
var data:Null<Bytes> = getNull();
|
||||||
|
checkReq(NullableSpodClass.manager.search($data == data));
|
||||||
|
var anEnum:Null<SEnum<SpodEnum>> = getNull();
|
||||||
|
checkReq(NullableSpodClass.manager.search($anEnum == anEnum));
|
||||||
|
|
||||||
|
var int:Null<Int> = getNull();
|
||||||
|
checkReq(NullableSpodClass.manager.search($int == int));
|
||||||
|
var double:Null<Float> = getNull();
|
||||||
|
checkReq(NullableSpodClass.manager.search($double == double));
|
||||||
|
var boolean:Null<Bool> = getNull();
|
||||||
|
checkReq(NullableSpodClass.manager.search($boolean == boolean));
|
||||||
|
var string:SNull<SString<255>> = getNull();
|
||||||
|
checkReq(NullableSpodClass.manager.search($string == string));
|
||||||
|
var date:SNull<SDateTime> = getNull();
|
||||||
|
checkReq(NullableSpodClass.manager.search($date == date));
|
||||||
|
var binary:SNull<SBinary> = getNull();
|
||||||
|
checkReq(NullableSpodClass.manager.search($binary == binary));
|
||||||
|
var abstractType:SNull<String> = getNull();
|
||||||
|
checkReq(NullableSpodClass.manager.search($abstractType == abstractType));
|
||||||
|
|
||||||
|
for (val in NullableSpodClass.manager.all()) {
|
||||||
|
val.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public function testIssue3828()
|
||||||
|
{
|
||||||
|
var u1 = new IssueC3828();
|
||||||
|
u1.insert();
|
||||||
|
var u2 = new IssueC3828();
|
||||||
|
u2.refUser = u1;
|
||||||
|
u2.insert();
|
||||||
|
var u1id = u1.id, u2id = u2.id;
|
||||||
|
u1 = null; u2 = null;
|
||||||
|
Manager.cleanup();
|
||||||
|
|
||||||
|
var u1 = IssueC3828.manager.get(u1id);
|
||||||
|
var u2 = IssueC3828.manager.search($refUser == u1).first();
|
||||||
|
Assert.equals(u1id, u1.id);
|
||||||
|
Assert.equals(u2id, u2.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public function testIssue6041()
|
||||||
|
{
|
||||||
|
var item = new Issue6041Table();
|
||||||
|
item.insert();
|
||||||
|
var result = Manager.cnx.request('SELECT * FROM Issue6041Table LIMIT 1');
|
||||||
|
var amount = 1;
|
||||||
|
for(row in result) {
|
||||||
|
Assert.isFalse(--amount < 0, "Invalid amount of rows in result");
|
||||||
|
}
|
||||||
|
Assert.equals(0, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public function testStringIdRel()
|
||||||
|
{
|
||||||
|
var s = new ClassWithStringId();
|
||||||
|
s.name = "first";
|
||||||
|
s.field = 1;
|
||||||
|
s.insert();
|
||||||
|
var v1 = new ClassWithStringIdRef();
|
||||||
|
v1.ref = s;
|
||||||
|
v1.insert();
|
||||||
|
var v2 = new ClassWithStringIdRef();
|
||||||
|
v2.ref = s;
|
||||||
|
v2.insert();
|
||||||
|
|
||||||
|
s = new ClassWithStringId();
|
||||||
|
s.name = "second";
|
||||||
|
s.field = 2;
|
||||||
|
s.insert();
|
||||||
|
v1 = new ClassWithStringIdRef();
|
||||||
|
v1.ref = s;
|
||||||
|
v1.insert();
|
||||||
|
s = null; v1 = null; v2 = null;
|
||||||
|
Manager.cleanup();
|
||||||
|
|
||||||
|
var first = ClassWithStringId.manager.search($name == "first");
|
||||||
|
Assert.equals(1, first.length);
|
||||||
|
var first = first.first();
|
||||||
|
Assert.equals(1, first.field);
|
||||||
|
var frel = ClassWithStringIdRef.manager.search($ref == first);
|
||||||
|
Assert.equals(2, frel.length);
|
||||||
|
for (rel in frel)
|
||||||
|
Assert.equals(first, rel.ref);
|
||||||
|
var frel2 = ClassWithStringIdRef.manager.search($ref_id == "first");
|
||||||
|
Assert.equals(2, frel2.length);
|
||||||
|
for (rel in frel2)
|
||||||
|
Assert.equals(first, rel.ref);
|
||||||
|
|
||||||
|
var second = ClassWithStringId.manager.search($name == "second");
|
||||||
|
Assert.equals(1, second.length);
|
||||||
|
var second = second.first();
|
||||||
|
Assert.equals(2, second.field);
|
||||||
|
var srel = ClassWithStringIdRef.manager.search($ref == second);
|
||||||
|
Assert.equals(1, srel.length);
|
||||||
|
for (rel in srel)
|
||||||
|
Assert.equals(second, rel.ref);
|
||||||
|
|
||||||
|
Assert.equals(-1, frel.array().indexOf(srel.first()));
|
||||||
|
second.delete();
|
||||||
|
for (r in srel) r.delete();
|
||||||
|
first.delete();
|
||||||
|
for (r in frel) r.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public function testEnum()
|
||||||
|
{
|
||||||
|
var c1 = new OtherSpodClass("first spod");
|
||||||
|
c1.insert();
|
||||||
|
var c2 = new OtherSpodClass("second spod");
|
||||||
|
c2.insert();
|
||||||
|
|
||||||
|
var scls = getDefaultClass();
|
||||||
|
var scls1 = scls;
|
||||||
|
scls.relation = c1;
|
||||||
|
scls.insert();
|
||||||
|
var id1 = scls.theId;
|
||||||
|
scls = getDefaultClass();
|
||||||
|
scls.relation = c1;
|
||||||
|
scls.insert();
|
||||||
|
|
||||||
|
scls1.next = scls;
|
||||||
|
scls1.update();
|
||||||
|
|
||||||
|
var id2 = scls.theId;
|
||||||
|
scls = getDefaultClass();
|
||||||
|
scls.relation = c1;
|
||||||
|
scls.next = scls1;
|
||||||
|
scls.anEnum = FirstValue;
|
||||||
|
scls.insert();
|
||||||
|
var id3 = scls.theId;
|
||||||
|
scls = null;
|
||||||
|
|
||||||
|
Manager.cleanup();
|
||||||
|
var r1s = [ for (c in MySpodClass.manager.search($anEnum == SecondValue,{orderBy:theId})) c.theId ];
|
||||||
|
Assert.deepEquals(r1s, [id1, id2]);
|
||||||
|
var r2s = MySpodClass.manager.search($anEnum == FirstValue);
|
||||||
|
Assert.equals(1, r2s.length);
|
||||||
|
Assert.equals(id3, r2s.first().theId);
|
||||||
|
Assert.equals(id1, r2s.first().next.theId);
|
||||||
|
Assert.equals(id2, r2s.first().next.next.theId);
|
||||||
|
|
||||||
|
var fv = getSecond();
|
||||||
|
var r1s = [ for (c in MySpodClass.manager.search($anEnum == fv,{orderBy:theId})) c.theId ];
|
||||||
|
Assert.deepEquals(r1s, [id1, id2]);
|
||||||
|
var r2s = MySpodClass.manager.search($anEnum == getFirst());
|
||||||
|
Assert.equals(1, r2s.length);
|
||||||
|
Assert.equals(id3, r2s.first().theId);
|
||||||
|
|
||||||
|
var ids = [id1,id2,id3];
|
||||||
|
var s = [ for (c in MySpodClass.manager.search( $anEnum == SecondValue || ($theId in ids) )) c.theId ];
|
||||||
|
s.sort(Reflect.compare);
|
||||||
|
Assert.deepEquals(s, [id1, id2, id3]);
|
||||||
|
|
||||||
|
r2s.first().delete();
|
||||||
|
for (v in MySpodClass.manager.search($anEnum == fv)) v.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFirst()
|
||||||
|
{
|
||||||
|
return FirstValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSecond()
|
||||||
|
{
|
||||||
|
return SecondValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public function testUpdate()
|
||||||
|
{
|
||||||
|
var c1 = new OtherSpodClass("first spod");
|
||||||
|
c1.insert();
|
||||||
|
var c2 = new OtherSpodClass("second spod");
|
||||||
|
c2.insert();
|
||||||
|
var scls = getDefaultClass();
|
||||||
|
scls.relation = c1;
|
||||||
|
scls.relationNullable = c2;
|
||||||
|
scls.insert();
|
||||||
|
|
||||||
|
var id = scls.theId;
|
||||||
|
|
||||||
|
//if no change made, update should return nothing
|
||||||
|
Assert.isNull(untyped MySpodClass.manager.getUpdateStatement(scls));
|
||||||
|
Manager.cleanup();
|
||||||
|
scls = MySpodClass.manager.get(id);
|
||||||
|
Assert.isNull(untyped MySpodClass.manager.getUpdateStatement(scls));
|
||||||
|
scls.delete();
|
||||||
|
|
||||||
|
//try now with null SData and null relation
|
||||||
|
var scls = new NullableSpodClass();
|
||||||
|
scls.insert();
|
||||||
|
|
||||||
|
var id = scls.theId;
|
||||||
|
|
||||||
|
//if no change made, update should return nothing
|
||||||
|
Assert.isNull(untyped NullableSpodClass.manager.getUpdateStatement(scls));
|
||||||
|
Manager.cleanup();
|
||||||
|
scls = NullableSpodClass.manager.get(id);
|
||||||
|
Assert.isNull(untyped NullableSpodClass.manager.getUpdateStatement(scls));
|
||||||
|
Assert.isNull(scls.data);
|
||||||
|
Assert.isNull(scls.relationNullable);
|
||||||
|
Assert.isNull(scls.abstractType);
|
||||||
|
Assert.isNull(scls.anEnum);
|
||||||
|
scls.delete();
|
||||||
|
|
||||||
|
//same thing with explicit null set
|
||||||
|
var scls = new NullableSpodClass();
|
||||||
|
scls.data = null;
|
||||||
|
scls.relationNullable = null;
|
||||||
|
scls.abstractType = null;
|
||||||
|
scls.anEnum = null;
|
||||||
|
scls.insert();
|
||||||
|
|
||||||
|
var id = scls.theId;
|
||||||
|
|
||||||
|
//if no change made, update should return nothing
|
||||||
|
Assert.isNull(untyped NullableSpodClass.manager.getUpdateStatement(scls));
|
||||||
|
Manager.cleanup();
|
||||||
|
scls = NullableSpodClass.manager.get(id);
|
||||||
|
Assert.isNull(untyped NullableSpodClass.manager.getUpdateStatement(scls));
|
||||||
|
Assert.isNull(scls.data);
|
||||||
|
Assert.isNull(scls.relationNullable);
|
||||||
|
Assert.isNull(scls.abstractType);
|
||||||
|
Assert.isNull(scls.anEnum);
|
||||||
|
Manager.cleanup();
|
||||||
|
|
||||||
|
scls = new NullableSpodClass();
|
||||||
|
scls.theId = id;
|
||||||
|
Assert.isNotNull(untyped NullableSpodClass.manager.getUpdateStatement(scls));
|
||||||
|
|
||||||
|
scls.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public function testSpodTypes()
|
||||||
|
{
|
||||||
|
var c1 = new OtherSpodClass("first spod");
|
||||||
|
c1.insert();
|
||||||
|
var c2 = new OtherSpodClass("second spod");
|
||||||
|
c2.insert();
|
||||||
|
|
||||||
|
var scls = getDefaultClass();
|
||||||
|
|
||||||
|
scls.relation = c1;
|
||||||
|
scls.relationNullable = c2;
|
||||||
|
scls.insert();
|
||||||
|
|
||||||
|
//after inserting, id must be filled
|
||||||
|
Assert.notEquals(0, scls.theId, pos());
|
||||||
|
Assert.isNotNull(scls.theId);
|
||||||
|
var theid = scls.theId;
|
||||||
|
|
||||||
|
c1 = c2 = null;
|
||||||
|
Manager.cleanup();
|
||||||
|
|
||||||
|
var cls1 = MySpodClass.manager.get(theid);
|
||||||
|
Assert.isNotNull(cls1, pos());
|
||||||
|
//after Manager.cleanup(), the instances should be different
|
||||||
|
Assert.isFalse(cls1 == scls, pos());
|
||||||
|
scls = null;
|
||||||
|
|
||||||
|
Assert.isInstanceOf(cls1.int, Int, pos());
|
||||||
|
Assert.equals(1, cls1.int, pos());
|
||||||
|
Assert.isInstanceOf(cls1.double, Float, pos());
|
||||||
|
Assert.equals(2.0, cls1.double, pos());
|
||||||
|
Assert.isInstanceOf(cls1.boolean, Bool, pos());
|
||||||
|
Assert.isTrue(cls1.boolean, pos());
|
||||||
|
Assert.isInstanceOf(cls1.string, String, pos());
|
||||||
|
Assert.equals("some string", cls1.string, pos());
|
||||||
|
Assert.isInstanceOf(cls1.abstractType, String, pos());
|
||||||
|
Assert.equals("other string", cls1.abstractType.get(), pos());
|
||||||
|
Assert.isNotNull(cls1.date, pos());
|
||||||
|
Assert.isInstanceOf(cls1.date, Date, pos());
|
||||||
|
Assert.equals(new Date(2012, 7, 30, 0, 0, 0).getTime(), cls1.date.getTime(), pos());
|
||||||
|
|
||||||
|
Assert.isInstanceOf(cls1.binary, Bytes, pos());
|
||||||
|
Assert.equals(0, cls1.binary.compare(Bytes.ofString("\x01\n\r'\x02")), pos());
|
||||||
|
Assert.isTrue(cls1.enumFlags.has(FirstValue), pos());
|
||||||
|
Assert.isFalse(cls1.enumFlags.has(SecondValue), pos());
|
||||||
|
Assert.isTrue(cls1.enumFlags.has(ThirdValue), pos());
|
||||||
|
|
||||||
|
Assert.isInstanceOf(cls1.data, Array, pos());
|
||||||
|
Assert.isInstanceOf(cls1.data[0], ComplexClass, pos());
|
||||||
|
|
||||||
|
Assert.equals("test", cls1.data[0].val.name, pos());
|
||||||
|
Assert.equals(4, cls1.data[0].val.array.length, pos());
|
||||||
|
Assert.equals("is", cls1.data[0].val.array[1], pos());
|
||||||
|
|
||||||
|
Assert.equals("first spod", cls1.relation.name, pos());
|
||||||
|
Assert.equals("second spod", cls1.relationNullable.name, pos());
|
||||||
|
|
||||||
|
Assert.equals(SecondValue, cls1.anEnum, pos());
|
||||||
|
Assert.isInstanceOf(cls1.anEnum, SpodEnum, pos());
|
||||||
|
|
||||||
|
Assert.equals("\000a", cls1.bytes.toString());
|
||||||
|
|
||||||
|
Assert.equals(MySpodClass.manager.select($anEnum == SecondValue), cls1, pos());
|
||||||
|
|
||||||
|
//test create a new class
|
||||||
|
var scls = getDefaultClass();
|
||||||
|
|
||||||
|
c1 = new OtherSpodClass("third spod");
|
||||||
|
c1.insert();
|
||||||
|
|
||||||
|
scls.relation = c1;
|
||||||
|
scls.insert();
|
||||||
|
|
||||||
|
scls = cls1 = null;
|
||||||
|
Manager.cleanup();
|
||||||
|
|
||||||
|
Assert.equals(2, MySpodClass.manager.all().length, pos());
|
||||||
|
var req = MySpodClass.manager.search({ relation: OtherSpodClass.manager.select({ name:"third spod"} ) });
|
||||||
|
Assert.equals(1, req.length, pos());
|
||||||
|
scls = req.first();
|
||||||
|
|
||||||
|
scls.relation.name = "Test";
|
||||||
|
scls.relation.update();
|
||||||
|
|
||||||
|
Assert.isNull(OtherSpodClass.manager.select({ name:"third spod" }), pos());
|
||||||
|
|
||||||
|
for (c in MySpodClass.manager.all())
|
||||||
|
c.delete();
|
||||||
|
for (c in OtherSpodClass.manager.all())
|
||||||
|
c.delete();
|
||||||
|
|
||||||
|
//issue #3598
|
||||||
|
var inexistent = MySpodClass.manager.get(1000,false);
|
||||||
|
Assert.isNull(inexistent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public function testDateQuery()
|
||||||
|
{
|
||||||
|
var other1 = new OtherSpodClass("required field");
|
||||||
|
other1.insert();
|
||||||
|
|
||||||
|
var now = Date.now();
|
||||||
|
var c1 = getDefaultClass();
|
||||||
|
c1.relation = other1;
|
||||||
|
c1.date = now;
|
||||||
|
c1.insert();
|
||||||
|
|
||||||
|
var c2 = getDefaultClass();
|
||||||
|
c2.relation = other1;
|
||||||
|
c2.date = DateTools.delta(now, DateTools.hours(1));
|
||||||
|
c2.insert();
|
||||||
|
|
||||||
|
var q = MySpodClass.manager.search($date > now);
|
||||||
|
Assert.equals(1, q.length, pos());
|
||||||
|
Assert.equals(c2, q.first(), pos());
|
||||||
|
|
||||||
|
q = MySpodClass.manager.search($date == now);
|
||||||
|
Assert.equals(1, q.length, pos());
|
||||||
|
Assert.equals(c1, q.first(), pos());
|
||||||
|
|
||||||
|
q = MySpodClass.manager.search($date >= now);
|
||||||
|
Assert.equals(2, q.length, pos());
|
||||||
|
Assert.equals(c1, q.first(), pos());
|
||||||
|
|
||||||
|
q = MySpodClass.manager.search($date >= DateTools.delta(now, DateTools.hours(2)));
|
||||||
|
Assert.equals(0, q.length, pos());
|
||||||
|
Assert.isNull(q.first(), pos());
|
||||||
|
|
||||||
|
c1.delete();
|
||||||
|
c2.delete();
|
||||||
|
other1.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public function testData()
|
||||||
|
{
|
||||||
|
var other1 = new OtherSpodClass("required field");
|
||||||
|
other1.insert();
|
||||||
|
|
||||||
|
var c1 = getDefaultClass();
|
||||||
|
c1.relation = other1;
|
||||||
|
c1.insert();
|
||||||
|
|
||||||
|
Assert.equals(1, c1.data.length, pos());
|
||||||
|
c1.data.pop();
|
||||||
|
c1.update();
|
||||||
|
|
||||||
|
Manager.cleanup();
|
||||||
|
c1 = null;
|
||||||
|
|
||||||
|
c1 = MySpodClass.manager.select($relation == other1);
|
||||||
|
Assert.equals(0, c1.data.length, pos());
|
||||||
|
c1.data.push(new ComplexClass({ name: "test1", array:["complex","field"] }));
|
||||||
|
c1.data.push(null);
|
||||||
|
Assert.equals(2, c1.data.length, pos());
|
||||||
|
c1.update();
|
||||||
|
|
||||||
|
Manager.cleanup();
|
||||||
|
c1 = null;
|
||||||
|
|
||||||
|
c1 = MySpodClass.manager.select($relation == other1);
|
||||||
|
Assert.equals(2, c1.data.length, pos());
|
||||||
|
Assert.equals("test1", c1.data[0].val.name, pos());
|
||||||
|
Assert.equals(2, c1.data[0].val.array.length, pos());
|
||||||
|
Assert.equals("complex", c1.data[0].val.array[0], pos());
|
||||||
|
Assert.isNull(c1.data[1], pos());
|
||||||
|
|
||||||
|
c1.delete();
|
||||||
|
other1.delete();
|
||||||
|
}
|
||||||
|
#end
|
||||||
|
|
||||||
|
/**
|
||||||
|
Check that relations are not affected by the analyzer
|
||||||
|
|
||||||
|
See: #6 and HaxeFoundation/haxe#6048
|
||||||
|
|
||||||
|
The way the analyzer transforms the expression (to prevent potential
|
||||||
|
side-effects) might change the context where `untyped __this__` is
|
||||||
|
evaluated.
|
||||||
|
**/
|
||||||
|
@Test
|
||||||
|
public function testIssue6()
|
||||||
|
{
|
||||||
|
var parent = new MySpodClass();
|
||||||
|
parent.relation = new OtherSpodClass("i");
|
||||||
|
|
||||||
|
Assert.isNotNull(parent.relation);
|
||||||
|
Assert.equals("i", parent.relation.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function pos(?p:haxe.PosInfos):haxe.PosInfos
|
||||||
|
{
|
||||||
|
p.fileName = p.fileName + "(" + Manager.cnx.dbName() +")";
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
import sys.db.Object;
|
||||||
|
import sys.db.Types;
|
||||||
|
|
||||||
|
@:keep class MySpodClass extends Object
|
||||||
|
{
|
||||||
|
public var theId:SId;
|
||||||
|
public var int:SInt;
|
||||||
|
public var double:SFloat;
|
||||||
|
public var boolean:SBool;
|
||||||
|
public var string:SString<255>;
|
||||||
|
public var date:SDateTime;
|
||||||
|
public var binary:SBinary;
|
||||||
|
public var abstractType:AbstractSpodTest<String>;
|
||||||
|
|
||||||
|
public var nullInt:SNull<Int>;
|
||||||
|
public var enumFlags:SFlags<SpodEnum>;
|
||||||
|
|
||||||
|
@:relation(rid) public var relation:OtherSpodClass;
|
||||||
|
@:relation(rnid) public var relationNullable:Null<OtherSpodClass>;
|
||||||
|
@:relation(spid) public var next:Null<MySpodClass>;
|
||||||
|
|
||||||
|
public var data:SData<Array<ComplexClass>>;
|
||||||
|
public var anEnum:SEnum<SpodEnum>;
|
||||||
|
public var bytes:SBytes<2>;
|
||||||
|
}
|
||||||
|
|
||||||
|
@:keep class NullableSpodClass extends Object
|
||||||
|
{
|
||||||
|
public var theId:SId;
|
||||||
|
@:relation(rnid) public var relationNullable:Null<OtherSpodClass>;
|
||||||
|
public var data:Null<SData<Array<ComplexClass>>>;
|
||||||
|
public var anEnum:Null<SEnum<SpodEnum>>;
|
||||||
|
|
||||||
|
public var int:SNull<SInt>;
|
||||||
|
public var double:SNull<SFloat>;
|
||||||
|
public var boolean:SNull<SBool>;
|
||||||
|
public var string:SNull<SString<255>>;
|
||||||
|
public var date:SNull<SDateTime>;
|
||||||
|
public var binary:SNull<SBinary>;
|
||||||
|
public var abstractType:SNull<AbstractSpodTest<String>>;
|
||||||
|
|
||||||
|
public var nullInt:SNull<Int>;
|
||||||
|
public var enumFlags:SNull<SFlags<SpodEnum>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
@:keep class ComplexClass
|
||||||
|
{
|
||||||
|
public var val : { name:String, array:Array<String> };
|
||||||
|
|
||||||
|
public function new(val)
|
||||||
|
{
|
||||||
|
this.val = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@:id(theid) @:keep class OtherSpodClass extends Object
|
||||||
|
{
|
||||||
|
public var theid:SInt;
|
||||||
|
public var name:SString<255>;
|
||||||
|
|
||||||
|
public function new(name:String)
|
||||||
|
{
|
||||||
|
super();
|
||||||
|
this.name =name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@:keep enum SpodEnum
|
||||||
|
{
|
||||||
|
FirstValue;
|
||||||
|
SecondValue;
|
||||||
|
ThirdValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract AbstractSpodTest<A>(A) from A
|
||||||
|
{
|
||||||
|
public function get():A
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@:id(name)
|
||||||
|
@:keep class ClassWithStringId extends Object
|
||||||
|
{
|
||||||
|
public var name:SString<255>;
|
||||||
|
public var field:SInt;
|
||||||
|
}
|
||||||
|
|
||||||
|
@:keep class ClassWithStringIdRef extends Object
|
||||||
|
{
|
||||||
|
public var id:SId;
|
||||||
|
@:relation(ref_id) public var ref:ClassWithStringId;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//issue #3828
|
||||||
|
@:keep @:skip class BaseIssueC3828 extends sys.db.Object {
|
||||||
|
public var id : SInt;
|
||||||
|
@:relation(ruid)
|
||||||
|
public var refUser : SNull<IssueC3828>;
|
||||||
|
}
|
||||||
|
|
||||||
|
@:keep class IssueC3828 extends BaseIssueC3828 {
|
||||||
|
}
|
||||||
|
|
||||||
|
@:keep class Issue6041Table extends Object {
|
||||||
|
public var id:SInt = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// issue #<to be numbered>
|
||||||
|
class TLazyIssueFoo extends sys.db.Object {
|
||||||
|
public var id:SId;
|
||||||
|
@:relation(bid) public var bar:TLazyIssueBar;
|
||||||
|
|
||||||
|
public function new(bar:TLazyIssueBar)
|
||||||
|
{
|
||||||
|
var lastFoo = TLazyIssueFoo.manager.select($bar == bar, { orderBy : -id, limit : 1 }, false);
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class TLazyIssueBar extends sys.db.Object {
|
||||||
|
public var id:SId;
|
||||||
|
public var initialized:SString<255> = "bar";
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,569 @@
|
|||||||
|
import sys.db.*;
|
||||||
|
import sys.db.Types;
|
||||||
|
import haxe.io.Bytes;
|
||||||
|
import haxe.EnumFlags;
|
||||||
|
import MySpodClass;
|
||||||
|
import hex.unittest.assertion.Assert;
|
||||||
|
import hex.unittest.notifier.*;
|
||||||
|
import hex.unittest.runner.*;
|
||||||
|
|
||||||
|
using Lambda;
|
||||||
|
|
||||||
|
class SQLiteTest
|
||||||
|
{
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public function before()
|
||||||
|
{
|
||||||
|
Manager.initialize();
|
||||||
|
Manager.cnx = sys.db.Sqlite.open("test.sqlite");
|
||||||
|
try Manager.cnx.request('DROP TABLE MySpodClass') catch(e:Dynamic) {}
|
||||||
|
try Manager.cnx.request('DROP TABLE OtherSpodClass') catch(e:Dynamic) {}
|
||||||
|
try Manager.cnx.request('DROP TABLE NullableSpodClass') catch(e:Dynamic) {}
|
||||||
|
try Manager.cnx.request('DROP TABLE ClassWithStringId') catch(e:Dynamic) {}
|
||||||
|
try Manager.cnx.request('DROP TABLE ClassWithStringIdRef') catch(e:Dynamic) {}
|
||||||
|
try Manager.cnx.request('DROP TABLE IssueC3828') catch(e:Dynamic) {}
|
||||||
|
try Manager.cnx.request('DROP TABLE Issue6041Table') catch(e:Dynamic) {}
|
||||||
|
TableCreate.create(MySpodClass.manager);
|
||||||
|
TableCreate.create(OtherSpodClass.manager);
|
||||||
|
TableCreate.create(NullableSpodClass.manager);
|
||||||
|
TableCreate.create(ClassWithStringId.manager);
|
||||||
|
TableCreate.create(ClassWithStringIdRef.manager);
|
||||||
|
TableCreate.create(IssueC3828.manager);
|
||||||
|
TableCreate.create(Issue6041Table.manager);
|
||||||
|
|
||||||
|
Manager.cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public function after()
|
||||||
|
{
|
||||||
|
Manager.cnx.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDefaultClass()
|
||||||
|
{
|
||||||
|
var scls = new MySpodClass();
|
||||||
|
scls.int = 1;
|
||||||
|
scls.double = 2.0;
|
||||||
|
scls.boolean = true;
|
||||||
|
scls.string = "some string";
|
||||||
|
scls.date = new Date(2012, 7, 30, 0, 0, 0);
|
||||||
|
scls.abstractType = "other string";
|
||||||
|
|
||||||
|
var bytes = Bytes.ofString("\x01\n\r'\x02");
|
||||||
|
scls.binary = bytes;
|
||||||
|
scls.enumFlags = EnumFlags.ofInt(0);
|
||||||
|
scls.enumFlags.set(FirstValue);
|
||||||
|
scls.enumFlags.set(ThirdValue);
|
||||||
|
scls.bytes = Bytes.ofString("\000a");
|
||||||
|
|
||||||
|
scls.data = [new ComplexClass( { name:"test", array:["this", "is", "a", "test"] } )];
|
||||||
|
scls.anEnum = SecondValue;
|
||||||
|
|
||||||
|
return scls;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDefaultNull() {
|
||||||
|
var scls = new NullableSpodClass();
|
||||||
|
scls.int = 1;
|
||||||
|
scls.double = 2.0;
|
||||||
|
scls.boolean = true;
|
||||||
|
scls.string = "some string";
|
||||||
|
scls.date = new Date(2012, 7, 30, 0, 0, 0);
|
||||||
|
scls.abstractType = "other string";
|
||||||
|
|
||||||
|
var bytes = Bytes.ofString("\x01\n\r'\x02");
|
||||||
|
scls.binary = bytes;
|
||||||
|
scls.enumFlags = EnumFlags.ofInt(0);
|
||||||
|
scls.enumFlags.set(FirstValue);
|
||||||
|
scls.enumFlags.set(ThirdValue);
|
||||||
|
|
||||||
|
scls.data = [new ComplexClass( { name:"test", array:["this", "is", "a", "test"] } )];
|
||||||
|
scls.anEnum = SecondValue;
|
||||||
|
return scls;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public function testNull() {
|
||||||
|
var n1 = getDefaultNull();
|
||||||
|
n1.insert();
|
||||||
|
var n2 = new NullableSpodClass();
|
||||||
|
n2.insert();
|
||||||
|
var id = n2.theId;
|
||||||
|
|
||||||
|
n1 = null; n2 = null;
|
||||||
|
Manager.cleanup();
|
||||||
|
|
||||||
|
var nullVal = getNull();
|
||||||
|
inline function checkReq(lst:List<NullableSpodClass>, ?nres=1, ?pos:haxe.PosInfos) {
|
||||||
|
Assert.equals(nres, lst.length, null, pos);
|
||||||
|
if (lst.length == 1) {
|
||||||
|
Assert.equals(id, lst.first().theId, null, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkReq(NullableSpodClass.manager.search($relationNullable == null), 2);
|
||||||
|
checkReq(NullableSpodClass.manager.search($data == null));
|
||||||
|
checkReq(NullableSpodClass.manager.search($anEnum == null));
|
||||||
|
|
||||||
|
checkReq(NullableSpodClass.manager.search($int == null));
|
||||||
|
checkReq(NullableSpodClass.manager.search($double == null));
|
||||||
|
checkReq(NullableSpodClass.manager.search($boolean == null));
|
||||||
|
checkReq(NullableSpodClass.manager.search($string == null));
|
||||||
|
checkReq(NullableSpodClass.manager.search($date == null));
|
||||||
|
checkReq(NullableSpodClass.manager.search($binary == null));
|
||||||
|
checkReq(NullableSpodClass.manager.search($abstractType == null));
|
||||||
|
|
||||||
|
checkReq(NullableSpodClass.manager.search($enumFlags == null));
|
||||||
|
|
||||||
|
|
||||||
|
var relationNullable:Null<OtherSpodClass> = getNull();
|
||||||
|
checkReq(NullableSpodClass.manager.search($relationNullable == relationNullable), 2);
|
||||||
|
var data:Null<Bytes> = getNull();
|
||||||
|
checkReq(NullableSpodClass.manager.search($data == data));
|
||||||
|
var anEnum:Null<SEnum<SpodEnum>> = getNull();
|
||||||
|
checkReq(NullableSpodClass.manager.search($anEnum == anEnum));
|
||||||
|
|
||||||
|
var int:Null<Int> = getNull();
|
||||||
|
checkReq(NullableSpodClass.manager.search($int == int));
|
||||||
|
var double:Null<Float> = getNull();
|
||||||
|
checkReq(NullableSpodClass.manager.search($double == double));
|
||||||
|
var boolean:Null<Bool> = getNull();
|
||||||
|
checkReq(NullableSpodClass.manager.search($boolean == boolean));
|
||||||
|
var string:SNull<SString<255>> = getNull();
|
||||||
|
checkReq(NullableSpodClass.manager.search($string == string));
|
||||||
|
var date:SNull<SDateTime> = getNull();
|
||||||
|
checkReq(NullableSpodClass.manager.search($date == date));
|
||||||
|
var binary:SNull<SBinary> = getNull();
|
||||||
|
checkReq(NullableSpodClass.manager.search($binary == binary));
|
||||||
|
var abstractType:SNull<String> = getNull();
|
||||||
|
checkReq(NullableSpodClass.manager.search($abstractType == abstractType));
|
||||||
|
|
||||||
|
for (val in NullableSpodClass.manager.all()) {
|
||||||
|
val.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getNull<T>():Null<T> {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public function testIssue3828()
|
||||||
|
{
|
||||||
|
var u1 = new IssueC3828();
|
||||||
|
u1.insert();
|
||||||
|
var u2 = new IssueC3828();
|
||||||
|
u2.refUser = u1;
|
||||||
|
u2.insert();
|
||||||
|
var u1id = u1.id, u2id = u2.id;
|
||||||
|
u1 = null; u2 = null;
|
||||||
|
Manager.cleanup();
|
||||||
|
|
||||||
|
var u1 = IssueC3828.manager.get(u1id);
|
||||||
|
var u2 = IssueC3828.manager.search($refUser == u1).first();
|
||||||
|
Assert.equals(u1id, u1.id);
|
||||||
|
Assert.equals(u2id, u2.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public function testIssue6041()
|
||||||
|
{
|
||||||
|
var item = new Issue6041Table();
|
||||||
|
item.insert();
|
||||||
|
var result = Manager.cnx.request('SELECT * FROM Issue6041Table LIMIT 1');
|
||||||
|
var amount = 1;
|
||||||
|
for(row in result) {
|
||||||
|
Assert.isFalse(--amount < 0, "Invalid amount of rows in result");
|
||||||
|
}
|
||||||
|
Assert.equals(0, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public function testStringIdRel()
|
||||||
|
{
|
||||||
|
var s = new ClassWithStringId();
|
||||||
|
s.name = "first";
|
||||||
|
s.field = 1;
|
||||||
|
s.insert();
|
||||||
|
var v1 = new ClassWithStringIdRef();
|
||||||
|
v1.ref = s;
|
||||||
|
v1.insert();
|
||||||
|
var v2 = new ClassWithStringIdRef();
|
||||||
|
v2.ref = s;
|
||||||
|
v2.insert();
|
||||||
|
|
||||||
|
s = new ClassWithStringId();
|
||||||
|
s.name = "second";
|
||||||
|
s.field = 2;
|
||||||
|
s.insert();
|
||||||
|
v1 = new ClassWithStringIdRef();
|
||||||
|
v1.ref = s;
|
||||||
|
v1.insert();
|
||||||
|
s = null; v1 = null; v2 = null;
|
||||||
|
Manager.cleanup();
|
||||||
|
|
||||||
|
var first = ClassWithStringId.manager.search($name == "first");
|
||||||
|
Assert.equals(1, first.length);
|
||||||
|
var first = first.first();
|
||||||
|
Assert.equals(1, first.field);
|
||||||
|
var frel = ClassWithStringIdRef.manager.search($ref == first);
|
||||||
|
Assert.equals(2, frel.length);
|
||||||
|
for (rel in frel)
|
||||||
|
Assert.equals(first, rel.ref);
|
||||||
|
var frel2 = ClassWithStringIdRef.manager.search($ref_id == "first");
|
||||||
|
Assert.equals(2, frel2.length);
|
||||||
|
for (rel in frel2)
|
||||||
|
Assert.equals(first, rel.ref);
|
||||||
|
|
||||||
|
var second = ClassWithStringId.manager.search($name == "second");
|
||||||
|
Assert.equals(1, second.length);
|
||||||
|
var second = second.first();
|
||||||
|
Assert.equals(2, second.field);
|
||||||
|
var srel = ClassWithStringIdRef.manager.search($ref == second);
|
||||||
|
Assert.equals(1, srel.length);
|
||||||
|
for (rel in srel)
|
||||||
|
Assert.equals(second, rel.ref);
|
||||||
|
|
||||||
|
Assert.equals(-1, frel.array().indexOf(srel.first()));
|
||||||
|
second.delete();
|
||||||
|
for (r in srel) r.delete();
|
||||||
|
first.delete();
|
||||||
|
for (r in frel) r.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public function testEnum()
|
||||||
|
{
|
||||||
|
var c1 = new OtherSpodClass("first spod");
|
||||||
|
c1.insert();
|
||||||
|
var c2 = new OtherSpodClass("second spod");
|
||||||
|
c2.insert();
|
||||||
|
|
||||||
|
var scls = getDefaultClass();
|
||||||
|
var scls1 = scls;
|
||||||
|
scls.relation = c1;
|
||||||
|
scls.insert();
|
||||||
|
var id1 = scls.theId;
|
||||||
|
scls = getDefaultClass();
|
||||||
|
scls.relation = c1;
|
||||||
|
scls.insert();
|
||||||
|
|
||||||
|
scls1.next = scls;
|
||||||
|
scls1.update();
|
||||||
|
|
||||||
|
var id2 = scls.theId;
|
||||||
|
scls = getDefaultClass();
|
||||||
|
scls.relation = c1;
|
||||||
|
scls.next = scls1;
|
||||||
|
scls.anEnum = FirstValue;
|
||||||
|
scls.insert();
|
||||||
|
var id3 = scls.theId;
|
||||||
|
scls = null;
|
||||||
|
|
||||||
|
Manager.cleanup();
|
||||||
|
var r1s = [ for (c in MySpodClass.manager.search($anEnum == SecondValue,{orderBy:theId})) c.theId ];
|
||||||
|
Assert.deepEquals(r1s, [id1, id2]);
|
||||||
|
var r2s = MySpodClass.manager.search($anEnum == FirstValue);
|
||||||
|
Assert.equals(1, r2s.length);
|
||||||
|
Assert.equals(id3, r2s.first().theId);
|
||||||
|
Assert.equals(id1, r2s.first().next.theId);
|
||||||
|
Assert.equals(id2, r2s.first().next.next.theId);
|
||||||
|
|
||||||
|
var fv = getSecond();
|
||||||
|
var r1s = [ for (c in MySpodClass.manager.search($anEnum == fv,{orderBy:theId})) c.theId ];
|
||||||
|
Assert.deepEquals(r1s, [id1, id2]);
|
||||||
|
var r2s = MySpodClass.manager.search($anEnum == getFirst());
|
||||||
|
Assert.equals(1, r2s.length);
|
||||||
|
Assert.equals(id3, r2s.first().theId);
|
||||||
|
|
||||||
|
var ids = [id1,id2,id3];
|
||||||
|
var s = [ for (c in MySpodClass.manager.search( $anEnum == SecondValue || ($theId in ids) )) c.theId ];
|
||||||
|
s.sort(Reflect.compare);
|
||||||
|
Assert.deepEquals(s, [id1, id2, id3]);
|
||||||
|
|
||||||
|
r2s.first().delete();
|
||||||
|
for (v in MySpodClass.manager.search($anEnum == fv)) v.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFirst()
|
||||||
|
{
|
||||||
|
return FirstValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSecond()
|
||||||
|
{
|
||||||
|
return SecondValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public function testUpdate()
|
||||||
|
{
|
||||||
|
var c1 = new OtherSpodClass("first spod");
|
||||||
|
c1.insert();
|
||||||
|
var c2 = new OtherSpodClass("second spod");
|
||||||
|
c2.insert();
|
||||||
|
var scls = getDefaultClass();
|
||||||
|
scls.relation = c1;
|
||||||
|
scls.relationNullable = c2;
|
||||||
|
scls.insert();
|
||||||
|
|
||||||
|
var id = scls.theId;
|
||||||
|
|
||||||
|
//if no change made, update should return nothing
|
||||||
|
Assert.isNull(untyped MySpodClass.manager.getUpdateStatement(scls));
|
||||||
|
Manager.cleanup();
|
||||||
|
scls = MySpodClass.manager.get(id);
|
||||||
|
Assert.isNull(untyped MySpodClass.manager.getUpdateStatement(scls));
|
||||||
|
scls.delete();
|
||||||
|
|
||||||
|
//try now with null SData and null relation
|
||||||
|
var scls = new NullableSpodClass();
|
||||||
|
scls.insert();
|
||||||
|
|
||||||
|
var id = scls.theId;
|
||||||
|
|
||||||
|
//if no change made, update should return nothing
|
||||||
|
Assert.isNull(untyped NullableSpodClass.manager.getUpdateStatement(scls));
|
||||||
|
Manager.cleanup();
|
||||||
|
scls = NullableSpodClass.manager.get(id);
|
||||||
|
Assert.isNull(untyped NullableSpodClass.manager.getUpdateStatement(scls));
|
||||||
|
Assert.isNull(scls.data);
|
||||||
|
Assert.isNull(scls.relationNullable);
|
||||||
|
Assert.isNull(scls.abstractType);
|
||||||
|
Assert.isNull(scls.anEnum);
|
||||||
|
scls.delete();
|
||||||
|
|
||||||
|
//same thing with explicit null set
|
||||||
|
var scls = new NullableSpodClass();
|
||||||
|
scls.data = null;
|
||||||
|
scls.relationNullable = null;
|
||||||
|
scls.abstractType = null;
|
||||||
|
scls.anEnum = null;
|
||||||
|
scls.insert();
|
||||||
|
|
||||||
|
var id = scls.theId;
|
||||||
|
|
||||||
|
//if no change made, update should return nothing
|
||||||
|
Assert.isNull(untyped NullableSpodClass.manager.getUpdateStatement(scls));
|
||||||
|
Manager.cleanup();
|
||||||
|
scls = NullableSpodClass.manager.get(id);
|
||||||
|
Assert.isNull(untyped NullableSpodClass.manager.getUpdateStatement(scls));
|
||||||
|
Assert.isNull(scls.data);
|
||||||
|
Assert.isNull(scls.relationNullable);
|
||||||
|
Assert.isNull(scls.abstractType);
|
||||||
|
Assert.isNull(scls.anEnum);
|
||||||
|
Manager.cleanup();
|
||||||
|
|
||||||
|
scls = new NullableSpodClass();
|
||||||
|
scls.theId = id;
|
||||||
|
Assert.isNotNull(untyped NullableSpodClass.manager.getUpdateStatement(scls));
|
||||||
|
|
||||||
|
scls.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public function testSpodTypes()
|
||||||
|
{
|
||||||
|
var c1 = new OtherSpodClass("first spod");
|
||||||
|
c1.insert();
|
||||||
|
var c2 = new OtherSpodClass("second spod");
|
||||||
|
c2.insert();
|
||||||
|
|
||||||
|
var scls = getDefaultClass();
|
||||||
|
|
||||||
|
scls.relation = c1;
|
||||||
|
scls.relationNullable = c2;
|
||||||
|
scls.insert();
|
||||||
|
|
||||||
|
//after inserting, id must be filled
|
||||||
|
Assert.notEquals(0, scls.theId, pos());
|
||||||
|
Assert.isNotNull(scls.theId);
|
||||||
|
var theid = scls.theId;
|
||||||
|
|
||||||
|
c1 = c2 = null;
|
||||||
|
Manager.cleanup();
|
||||||
|
|
||||||
|
var cls1 = MySpodClass.manager.get(theid);
|
||||||
|
Assert.isNotNull(cls1, pos());
|
||||||
|
//after Manager.cleanup(), the instances should be different
|
||||||
|
Assert.isFalse(cls1 == scls, pos());
|
||||||
|
scls = null;
|
||||||
|
|
||||||
|
Assert.isInstanceOf(cls1.int, Int, pos());
|
||||||
|
Assert.equals(1, cls1.int, pos());
|
||||||
|
Assert.isInstanceOf(cls1.double, Float, pos());
|
||||||
|
Assert.equals(2.0, cls1.double, pos());
|
||||||
|
Assert.isInstanceOf(cls1.boolean, Bool, pos());
|
||||||
|
Assert.isTrue(cls1.boolean, pos());
|
||||||
|
Assert.isInstanceOf(cls1.string, String, pos());
|
||||||
|
Assert.equals("some string", cls1.string, pos());
|
||||||
|
Assert.isInstanceOf(cls1.abstractType, String, pos());
|
||||||
|
Assert.equals("other string", cls1.abstractType.get(), pos());
|
||||||
|
Assert.isNotNull(cls1.date, pos());
|
||||||
|
Assert.isInstanceOf(cls1.date, Date, pos());
|
||||||
|
#if !php
|
||||||
|
// TODO : this fails with PHP7
|
||||||
|
Assert.equals(new Date(2012, 7, 30, 0, 0, 0).getTime(), cls1.date.getTime(), pos());
|
||||||
|
#end
|
||||||
|
|
||||||
|
Assert.isInstanceOf(cls1.binary, Bytes, pos());
|
||||||
|
Assert.equals(0, cls1.binary.compare(Bytes.ofString("\x01\n\r'\x02")), pos());
|
||||||
|
Assert.isTrue(cls1.enumFlags.has(FirstValue), pos());
|
||||||
|
Assert.isFalse(cls1.enumFlags.has(SecondValue), pos());
|
||||||
|
Assert.isTrue(cls1.enumFlags.has(ThirdValue), pos());
|
||||||
|
|
||||||
|
Assert.isInstanceOf(cls1.data, Array, pos());
|
||||||
|
Assert.isInstanceOf(cls1.data[0], ComplexClass, pos());
|
||||||
|
|
||||||
|
Assert.equals("test", cls1.data[0].val.name, pos());
|
||||||
|
Assert.equals(4, cls1.data[0].val.array.length, pos());
|
||||||
|
Assert.equals("is", cls1.data[0].val.array[1], pos());
|
||||||
|
|
||||||
|
Assert.equals("first spod", cls1.relation.name, pos());
|
||||||
|
Assert.equals("second spod", cls1.relationNullable.name, pos());
|
||||||
|
|
||||||
|
Assert.equals(SecondValue, cls1.anEnum, pos());
|
||||||
|
Assert.isInstanceOf(cls1.anEnum, SpodEnum, pos());
|
||||||
|
|
||||||
|
Assert.equals("\000a", cls1.bytes.toString());
|
||||||
|
|
||||||
|
Assert.equals(MySpodClass.manager.select($anEnum == SecondValue), cls1, pos());
|
||||||
|
|
||||||
|
//test create a new class
|
||||||
|
var scls = getDefaultClass();
|
||||||
|
|
||||||
|
c1 = new OtherSpodClass("third spod");
|
||||||
|
c1.insert();
|
||||||
|
|
||||||
|
scls.relation = c1;
|
||||||
|
scls.insert();
|
||||||
|
|
||||||
|
scls = cls1 = null;
|
||||||
|
Manager.cleanup();
|
||||||
|
|
||||||
|
Assert.equals(2, MySpodClass.manager.all().length, pos());
|
||||||
|
var req = MySpodClass.manager.search({ relation: OtherSpodClass.manager.select({ name:"third spod"} ) });
|
||||||
|
Assert.equals(1, req.length, pos());
|
||||||
|
scls = req.first();
|
||||||
|
|
||||||
|
scls.relation.name = "Test";
|
||||||
|
scls.relation.update();
|
||||||
|
|
||||||
|
Assert.isNull(OtherSpodClass.manager.select({ name:"third spod" }), pos());
|
||||||
|
|
||||||
|
for (c in MySpodClass.manager.all())
|
||||||
|
c.delete();
|
||||||
|
for (c in OtherSpodClass.manager.all())
|
||||||
|
c.delete();
|
||||||
|
|
||||||
|
//issue #3598
|
||||||
|
var inexistent = MySpodClass.manager.get(1000,false);
|
||||||
|
Assert.isNull(inexistent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public function testDateQuery()
|
||||||
|
{
|
||||||
|
var other1 = new OtherSpodClass("required field");
|
||||||
|
other1.insert();
|
||||||
|
|
||||||
|
var now = Date.now();
|
||||||
|
var c1 = getDefaultClass();
|
||||||
|
c1.relation = other1;
|
||||||
|
c1.date = now;
|
||||||
|
c1.insert();
|
||||||
|
|
||||||
|
var c2 = getDefaultClass();
|
||||||
|
c2.relation = other1;
|
||||||
|
c2.date = DateTools.delta(now, DateTools.hours(1));
|
||||||
|
c2.insert();
|
||||||
|
|
||||||
|
#if !php
|
||||||
|
// TODO : this fails with PHP7
|
||||||
|
var q = MySpodClass.manager.search($date > now);
|
||||||
|
Assert.equals(1, q.length, pos());
|
||||||
|
Assert.equals(c2, q.first(), pos());
|
||||||
|
|
||||||
|
q = MySpodClass.manager.search($date == now);
|
||||||
|
Assert.equals(1, q.length, pos());
|
||||||
|
Assert.equals(c1, q.first(), pos());
|
||||||
|
|
||||||
|
q = MySpodClass.manager.search($date >= now);
|
||||||
|
Assert.equals(2, q.length, pos());
|
||||||
|
Assert.equals(c1, q.first(), pos());
|
||||||
|
|
||||||
|
q = MySpodClass.manager.search($date >= DateTools.delta(now, DateTools.hours(2)));
|
||||||
|
Assert.equals(0, q.length, pos());
|
||||||
|
Assert.isNull(q.first(), pos());
|
||||||
|
#end
|
||||||
|
|
||||||
|
c1.delete();
|
||||||
|
c2.delete();
|
||||||
|
other1.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public function testData()
|
||||||
|
{
|
||||||
|
var other1 = new OtherSpodClass("required field");
|
||||||
|
other1.insert();
|
||||||
|
|
||||||
|
var c1 = getDefaultClass();
|
||||||
|
c1.relation = other1;
|
||||||
|
c1.insert();
|
||||||
|
|
||||||
|
Assert.equals(1, c1.data.length, pos());
|
||||||
|
c1.data.pop();
|
||||||
|
c1.update();
|
||||||
|
|
||||||
|
Manager.cleanup();
|
||||||
|
c1 = null;
|
||||||
|
|
||||||
|
c1 = MySpodClass.manager.select($relation == other1);
|
||||||
|
Assert.equals(0, c1.data.length, pos());
|
||||||
|
c1.data.push(new ComplexClass({ name: "test1", array:["complex","field"] }));
|
||||||
|
c1.data.push(null);
|
||||||
|
Assert.equals(2, c1.data.length, pos());
|
||||||
|
c1.update();
|
||||||
|
|
||||||
|
Manager.cleanup();
|
||||||
|
c1 = null;
|
||||||
|
|
||||||
|
c1 = MySpodClass.manager.select($relation == other1);
|
||||||
|
Assert.equals(2, c1.data.length, pos());
|
||||||
|
Assert.equals("test1", c1.data[0].val.name, pos());
|
||||||
|
Assert.equals(2, c1.data[0].val.array.length, pos());
|
||||||
|
Assert.equals("complex", c1.data[0].val.array[0], pos());
|
||||||
|
Assert.isNull(c1.data[1], pos());
|
||||||
|
|
||||||
|
c1.delete();
|
||||||
|
other1.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Check that relations are not affected by the analyzer
|
||||||
|
|
||||||
|
See: #6 and HaxeFoundation/haxe#6048
|
||||||
|
|
||||||
|
The way the analyzer transforms the expression (to prevent potential
|
||||||
|
side-effects) might change the context where `untyped __this__` is
|
||||||
|
evaluated.
|
||||||
|
**/
|
||||||
|
@Test
|
||||||
|
public function testIssue6()
|
||||||
|
{
|
||||||
|
var parent = new MySpodClass();
|
||||||
|
parent.relation = new OtherSpodClass("i");
|
||||||
|
|
||||||
|
Assert.isNotNull(parent.relation);
|
||||||
|
Assert.equals("i", parent.relation.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function pos(?p:haxe.PosInfos):haxe.PosInfos
|
||||||
|
{
|
||||||
|
p.fileName = p.fileName + "(" + Manager.cnx.dbName() +")";
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
-cp src
|
||||||
|
-cp test
|
||||||
|
-lib hexunit:0.35.0
|
||||||
|
-lib jstack
|
||||||
|
-main Main
|
||||||
|
-D php7
|
||||||
|
-D JSTACK_FORCE
|
||||||
|
-php ./
|
||||||
Reference in New Issue
Block a user