Skip to content

Commit

Permalink
Merge pull request #130 from kevinresol/transactions
Browse files Browse the repository at this point in the history
Initial attempt to implement transactions
  • Loading branch information
kevinresol authored Sep 16, 2021
2 parents 0aa3fdc + 1277593 commit a2d2770
Show file tree
Hide file tree
Showing 37 changed files with 795 additions and 271 deletions.
2 changes: 1 addition & 1 deletion .haxerc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"version": "4.1.3",
"version": "4.2.1",
"resolveLibs": "scoped"
}
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@

This library embeds SQL right into the Haxe language. Think LINQ (not the syntax sugar, but the framework).

**Breaking Change (2021-09-16)**

[Transaction](#transactions) is now supported with a minor breaking change.
Migration guide: https://github.com/haxetink/tink_sql/pull/130#issuecomment-822971910

## Motivation

Most developers tend to dislike SQL, at least for a significant part of their career. A symptom of that are ever-repeating attempts to hide the database layer behind ORMs, with very limited success.
Expand Down Expand Up @@ -100,3 +105,22 @@ var db = new Db('db_name', driver);
- `db.User.stream(limit, orderBy): tink.streams.RealStream<User>;`

... to be continued ...

## Transactions

```haxe
// transaction with a commit
db.transaction(trx -> {
trx.User.insertOne(...).next(id -> Commit(id)); // user is inserted
});
// transaction with explicit rollback
db.transaction(trx -> {
trx.User.insertOne(...).next(_ -> Rollback); // user insert is rolled back
});
// transaction with implicit rollback upon error
db.transaction(trx -> {
trx.User.insertOne(...).next(_ -> new Error('Aborted')); // user insert is rolled back
});
```
6 changes: 3 additions & 3 deletions haxe_libraries/tink_core.hxml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# @install: lix --silent download "gh://github.com/haxetink/tink_core#914f3cda113652bfa62226664fae49e17bd1730b" into tink_core/2.0.0-rc.3/github/914f3cda113652bfa62226664fae49e17bd1730b
-cp ${HAXE_LIBCACHE}/tink_core/2.0.0-rc.3/github/914f3cda113652bfa62226664fae49e17bd1730b/src
-D tink_core=2.0.0-rc.3
# @install: lix --silent download "gh://github.com/haxetink/tink_core#8fb0b9aa4de933614b5a04cc88da871b89cb8c6a" into tink_core/2.0.2/github/8fb0b9aa4de933614b5a04cc88da871b89cb8c6a
-cp ${HAXE_LIBCACHE}/tink_core/2.0.2/github/8fb0b9aa4de933614b5a04cc88da871b89cb8c6a/src
-D tink_core=2.0.2
5 changes: 5 additions & 0 deletions src/tink/sql/Connection.hx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ package tink.sql;
import tink.sql.Query;
import tink.sql.format.Formatter;

using tink.CoreApi;

interface ConnectionPool<Db> extends Connection<Db> {
function isolate():Pair<Connection<Db>, CallbackLink>;
}
interface Connection<Db> {
function getFormatter():Formatter<{}, {}>;
function execute<Result>(query:Query<Db, Result>):Result;
Expand Down
40 changes: 2 additions & 38 deletions src/tink/sql/Database.hx
100755 → 100644
Original file line number Diff line number Diff line change
@@ -1,40 +1,4 @@
package tink.sql;

#if macro
import haxe.macro.Expr;
#end
import tink.core.Error;
import tink.sql.Info;


@:autoBuild(tink.sql.macros.DatabaseBuilder.build())
class Database implements DatabaseInfo {

public var name(default, null):String;

// To type this correctly we'd need a self type #4474 or unnecessary macros
var cnx:Connection<Dynamic>;
var tables:Map<String, TableInfo>;
var driver:Driver;

function new(name, driver, tables) {
this.name = name;
this.driver = driver;
this.tables = tables;
}

public function tableNames():Iterable<String>
return {
iterator: function () return tables.keys()
};

public function tableInfo(name:String):TableInfo
return switch tables[name] {
case null: throw new Error(NotFound, 'Table `${this.name}.$name` not found');
case v: cast v;
}

macro public function from(ethis:Expr, target:Expr)
return tink.sql.macros.Targets.from(ethis, target, macro $ethis.cnx);

}
@:genericBuild(tink.sql.Database.build())
class Database<T> {}
39 changes: 39 additions & 0 deletions src/tink/sql/Database.macro.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package tink.sql;

import haxe.macro.Expr;
import haxe.macro.Context;
import tink.macro.BuildCache;
import tink.sql.macros.Helper;

using tink.MacroApi;

class Database {
public static function build() {
return BuildCache.getType('tink.sql.Database', (ctx:BuildContext) -> {
final name = ctx.name;
final ct = ctx.type.toComplex();
final def = macro class $name extends tink.sql.Transaction<$ct> {
public static final INFO = ${Helper.typePathToExpr(switch macro:tink.sql.Transaction<$ct> {case TPath(tp): tp; case _: null;}, ctx.pos)}.INFO;

public final __name:String;
public final __info:tink.sql.Info.DatabaseInfo;
public final __pool:tink.sql.Connection.ConnectionPool<$ct>;

public function new(name, driver:tink.sql.Driver) {
super(__pool = driver.open(__name = name, __info = INFO.instantiate(name)));
}

public inline function getName() return __name;
public inline function getInfo() return __info;

public function transaction<T>(run:tink.sql.Transaction<$ct>->tink.core.Promise<tink.sql.Transaction.TransactionEnd<T>>):tink.core.Promise<tink.sql.Transaction.TransactionEnd<T>> {
return tink.sql.Transaction.TransactionTools.transaction(__pool, isolated -> run(new tink.sql.Transaction<$ct>(isolated)));
}
}

// trace(new haxe.macro.Printer().printTypeDefinition(def));
def.pack = ['tink', 'sql'];
def;
});
}
}
4 changes: 4 additions & 0 deletions src/tink/sql/DatabaseDefinition.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package tink.sql;

@:autoBuild(tink.sql.DatabaseDefinition.build())
interface DatabaseDefinition {}
57 changes: 57 additions & 0 deletions src/tink/sql/DatabaseDefinition.macro.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package tink.sql;

import haxe.macro.Expr;
import haxe.macro.Context;
import tink.macro.ClassBuilder;

using tink.MacroApi;

class DatabaseDefinition {
public static function build() {
final c = new ClassBuilder();

for (t in c.target.meta.extract(':tables'))
for (p in t.params) {
var tp = p.toString().asTypePath();
c.addMember({
pos: p.pos,
name: switch tp { case { sub: null, name: name } | { sub: name } : name; },
meta: [{ name: ':table', pos: p.pos, params: [] }],
kind: FVar(TPath(tp)),
});
}

for (m in c) if(!m.isStatic) {
function extractMeta(name:String) {
return switch (m:Field).meta.getValues(name) {
case []: null;
case [[]]: m.name;
case [[v]]: v.getName().sure();
default: m.pos.error('Invalid use of @$name');
}
}

switch extractMeta(':table') {
case null:
case table:
var type = TAnonymous([{
name : m.name,
pos: m.pos,
kind: FVar(m.getVar().sure().type),
}]);
m.kind = FVar(macro : tink.sql.Table<$type>);
m.isFinal = true;
}

switch extractMeta(':procedure') {
case null:
case procedure:
final type = m.getVar().sure().type;
m.kind = FVar(macro : tink.sql.Procedure<$type>);
m.isFinal = true;
}
}

return c.export();
}
}
47 changes: 47 additions & 0 deletions src/tink/sql/DatabaseInfo.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package tink.sql;

import tink.sql.Info;

using tink.CoreApi;

class DatabaseStaticInfo implements DatabaseInfo {

final tables:Map<String, TableInfo>;

public function new(tables) {
this.tables = tables;
}

public function instantiate(name) {
return new DatabaseInstanceInfo(name, tables);
}

public function tableNames():Iterable<String>
return {
iterator: function () return tables.keys()
};

public function tableInfo(name:String):TableInfo
return switch tables[name] {
case null: throw new Error(NotFound, 'Table `${nameOfTable(name)}` not found');
case v: cast v;
}

function nameOfTable(tbl:String) {
return tbl;
}
}

class DatabaseInstanceInfo extends DatabaseStaticInfo {

final name:String;

public function new(name, tables) {
super(tables);
this.name = name;
}

override function nameOfTable(tbl:String) {
return '$name.$tbl';
}
}
2 changes: 1 addition & 1 deletion src/tink/sql/Dataset.hx
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ class Limited<Fields, Result:{}, Db> extends Dataset<Fields, Result, Db> {
class Dataset<Fields, Result:{}, Db> {

var cnx:Connection<Db>;

function new(cnx)
this.cnx = cnx;

Expand Down
2 changes: 1 addition & 1 deletion src/tink/sql/Driver.hx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package tink.sql;
import tink.sql.Info.DatabaseInfo;
interface Driver {
var type(default, null):DriverType;
function open<Db:DatabaseInfo>(name:String, info:Db):Connection<Db>;
function open<Db>(name:String, info:DatabaseInfo):Connection.ConnectionPool<Db>;
}

enum DriverType {
Expand Down
8 changes: 8 additions & 0 deletions src/tink/sql/Query.hx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import tink.streams.RealStream;
using tink.CoreApi;

enum Query<Db, Result> {
// Multi(queries: Array<Query<Db, Any>>):Query<Db, Promise<Noise>>;
Union<Row:{}>(union:UnionOperation<Db, Row>):Query<Db, RealStream<Row>>;
Select<Row:{}>(select:SelectOperation<Db, Row>):Query<Db, RealStream<Row>>;
Insert<Row:{}>(insert:InsertOperation<Db, Row>):Query<Db, Promise<Id<Row>>>;
Expand All @@ -21,6 +22,7 @@ enum Query<Db, Result> {
AlterTable<Row:{}>(table:TableInfo, changes:Array<AlterTableOperation>):Query<Db, Promise<Noise>>;
ShowColumns<Row:{}>(from:TableInfo):Query<Db, Promise<Array<Column>>>;
ShowIndex<Row:{}>(from:TableInfo):Query<Db, Promise<Array<Key>>>;
Transaction(transaction:TransactionOperation):Query<Db, Promise<Noise>>;
}

typedef UnionOperation<Db, Row:{}> = {
Expand Down Expand Up @@ -90,4 +92,10 @@ enum AlterTableOperation {
AlterColumn(to:Column, ?from:Column);
DropColumn(col:Column);
DropKey(key:Key);
}

enum TransactionOperation {
Start;
Commit;
Rollback;
}
48 changes: 27 additions & 21 deletions src/tink/sql/Table.hx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class TableSource<Fields, Filter:(Fields->Condition), Row:{}, Db>

public var name(default, null):TableName<Row>;
var alias:Null<String>;
public final info:AdhocTableInfo;
public final info:TableInstanceInfo;

function new(cnx, name, alias, fields, info) {
this.name = name;
Expand Down Expand Up @@ -167,19 +167,37 @@ abstract TableName<Row>(String) to String {



class AdhocTableInfo implements TableInfo {
class TableStaticInfo {

final columns:Array<Column>;
final keys:Array<Key>;

public function new(columns, keys) {
this.columns = columns;
this.keys = keys;
}

@:noCompletion
public function getColumns():Array<Column>
return columns;

@:noCompletion
public function columnNames():Array<String>
return [for(c in columns) c.name];

@:noCompletion
public function getKeys():Array<Key>
return keys;
}

class TableInstanceInfo extends TableStaticInfo implements TableInfo {
final name:String;
final alias:String;
final _getColumns:()->Array<Column>;
final _columnNames:()->Array<String>;
final _getKeys:()->Array<Key>;

public function new(name, alias, getColumns, columnNames, getKeys) {
public function new(name, alias, columns, keys) {
super(columns, keys);
this.name = name;
this.alias = alias;
_getColumns = getColumns;
_columnNames = columnNames;
_getKeys = getKeys;
}

// TableInfo
Expand All @@ -191,16 +209,4 @@ class AdhocTableInfo implements TableInfo {
@:noCompletion
public function getAlias():Null<String>
return alias;

@:noCompletion
public function getColumns():Array<Column>
return _getColumns();

@:noCompletion
public function columnNames():Array<String>
return _columnNames();

@:noCompletion
public function getKeys():Array<Key>
return _getKeys();
}
Loading

0 comments on commit a2d2770

Please sign in to comment.