Skip to content

haxetink/tink_sql

Repository files navigation

CI Gitter

Tinkerbell SQL

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

Breaking Change (2021-09-16)

Transaction is now supported with a minor breaking change. Migration guide: #130 (comment)

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.

Relational databases are however a very powerful concept, that was pioneered over 40 years ago.

Tink SQL has been built to embrace using SQL for your database interactions, but in a type safe way that fits with the Haxe ecosystem.

Defining your schema

Define a database like so:

import tink.sql.Types;

typedef User = {
  var id:Id<User>;
  var name:VarChar<255>;
  @:unique var email:VarChar<255>;
  var password:VarChar<255>;
}

typedef Post = {
  var id:Id<Post>;
  var author:Id<User>;
  var title:LongText;
  var url:VarChar<255>;
}

typedef Tag = {
  var id:Id<Tag>;
  var name:VarChar<20>;
  var desc:Null<Text>;
}

typedef PostTags = {
  var post:Id<Post>;
  var tag:Id<Tag>;
}

@:tables(User, Post, Tag, PostTags)
class Db extends tink.sql.Database {}

Redefining table names

class Db extends tink.sql.Database {
  @:table("blog_users") var user:User;
  @:table("blog_posts") var post:Post;
  @:table("news_tags") var tag:Tag;
  @:table("post_tags") var postTags:PostTags;
}

Connecting to the database

import tink.sql.drivers.MySql;

var driver = new tink.sql.drivers.MySql({
  user: 'user',
  password: 'pass'
});
var db = new Db('db_name', driver);

Tables API

  • Table setup
    • db.User.create(): Promise<Noise>;
    • db.User.drop(): Promise<Noise>;
    • db.User.truncate(): Promise<Noise>;
  • Selecting
    • db.User.count(): Promise<Int>;
    • db.User.all(limit, orderBy): Promise<Array<User>>;
    • db.User.first(orderBy): Promise<User>;
    • db.User.where(filter)
    • db.User.select(f:Fields->Selection<Row>)
      • Example, select name of user: db.User.select({name: User.name}).where(User.id == 1).first()
  • Writing
    • db.User.insertOne(row: User): Promise<Id<User>>;
    • db.User.insertMany(rows: Array<User>): Promise<Id<User>>;
    • db.User.update(f:Fields->Update<Row>, options:{ where: Filter, ?max:Int }): Promise<{rowsAffected: Int}>;
      • Example, rename all users called 'Dave' to 'Donald': db.User.update(function (u) return [u.name.set('Donald')], { where: function (u) return u.name == 'Dave' } );
    • db.User.delete(options:{ where: Filter, ?max:Int }): Promise<{rowsAffected: Int}>;
  • Advanced Selecting
    • db.User.as(alias);
    • db.User.join(db.User).on(id == copiedFrom).all();
    • db.User.leftJoin(db.User);
    • db.User.rightJoin(db.User);
    • db.User.stream(limit, orderBy): tink.streams.RealStream<User>;

... to be continued ...

Transactions

// 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
});