From b73d046e0ab04e92fbccc839ffa150863f9cb104 Mon Sep 17 00:00:00 2001
From: Dave Mc Nicoll <info@mcnd.ca>
Date: Wed, 21 Aug 2019 16:13:00 -0400
Subject: [PATCH] - First draft of current WIP

---
 composer.json                                |  18 +
 src/Adapter/AdapterInterface.php             |  10 +
 src/Adapter/MariaDB.php                      |   7 +
 src/Adapter/MySQL.php                        |  77 ++++
 src/Annotation/Annotation.php                |   5 +
 src/Annotation/AnnotationReader.php          | 103 +++++
 src/Annotation/Classes/Collation.php         |   7 +
 src/Annotation/Classes/Method.php            |   7 +
 src/Annotation/Classes/Table.php             |  15 +
 src/Annotation/Property/Field.php            |  29 ++
 src/Annotation/Property/Field/CreatedAt.php  |  13 +
 src/Annotation/Property/Field/ForeignKey.php |  19 +
 src/Annotation/Property/Field/Id.php         |  15 +
 src/Annotation/Property/Field/UpdatedAt.php  |  14 +
 src/Annotation/Property/GroupBy.php          |  15 +
 src/Annotation/Property/OrderBy.php          |  23 ++
 src/Annotation/Property/Relation.php         |  29 ++
 src/Annotation/Property/Where.php            |  15 +
 src/Common/ArrayObjectTrait.php              | 212 ++++++++++
 src/Common/EntityField.php                   |  56 +++
 src/Common/EntityResolver.php                | 185 +++++++++
 src/Common/ObjectReflection.php              | 273 +++++++++++++
 src/Common/PdoObject.php                     |  45 +++
 src/Common/Sql.php                           |  67 +++
 src/ConnectionAdapter.php                    |  87 ++++
 src/EntityCollection.php                     |   8 +
 src/EntityTrait.php                          |  82 ++++
 src/Modeler/Field.php                        |   0
 src/Modeler/Query.php                        | 404 +++++++++++++++++++
 src/Modeler/Schema.php                       |  21 +
 src/Query/Explain.php                        |  17 +
 src/Query/Fragment.php                       |  15 +
 src/Query/From.php                           |  43 ++
 src/Query/GroupBy.php                        |  29 ++
 src/Query/Having.php                         |   7 +
 src/Query/Insert.php                         |   9 +
 src/Query/Join.php                           |  28 ++
 src/Query/Like.php                           |   7 +
 src/Query/Limit.php                          |  23 ++
 src/Query/MySQL/Replace.php                  |  17 +
 src/Query/Offset.php                         |  23 ++
 src/Query/OrderBy.php                        |  34 ++
 src/Query/Select.php                         |  44 ++
 src/Query/Where.php                          | 151 +++++++
 src/QueryBuilder.php                         | 182 +++++++++
 src/Repository.php                           | 254 ++++++++++++
 src/Ulmus.php                                |  53 +++
 47 files changed, 2797 insertions(+)
 create mode 100644 composer.json
 create mode 100644 src/Adapter/AdapterInterface.php
 create mode 100644 src/Adapter/MariaDB.php
 create mode 100644 src/Adapter/MySQL.php
 create mode 100644 src/Annotation/Annotation.php
 create mode 100644 src/Annotation/AnnotationReader.php
 create mode 100644 src/Annotation/Classes/Collation.php
 create mode 100644 src/Annotation/Classes/Method.php
 create mode 100644 src/Annotation/Classes/Table.php
 create mode 100644 src/Annotation/Property/Field.php
 create mode 100644 src/Annotation/Property/Field/CreatedAt.php
 create mode 100644 src/Annotation/Property/Field/ForeignKey.php
 create mode 100644 src/Annotation/Property/Field/Id.php
 create mode 100644 src/Annotation/Property/Field/UpdatedAt.php
 create mode 100644 src/Annotation/Property/GroupBy.php
 create mode 100644 src/Annotation/Property/OrderBy.php
 create mode 100644 src/Annotation/Property/Relation.php
 create mode 100644 src/Annotation/Property/Where.php
 create mode 100644 src/Common/ArrayObjectTrait.php
 create mode 100644 src/Common/EntityField.php
 create mode 100644 src/Common/EntityResolver.php
 create mode 100644 src/Common/ObjectReflection.php
 create mode 100644 src/Common/PdoObject.php
 create mode 100644 src/Common/Sql.php
 create mode 100644 src/ConnectionAdapter.php
 create mode 100644 src/EntityCollection.php
 create mode 100644 src/EntityTrait.php
 create mode 100644 src/Modeler/Field.php
 create mode 100644 src/Modeler/Query.php
 create mode 100644 src/Modeler/Schema.php
 create mode 100644 src/Query/Explain.php
 create mode 100644 src/Query/Fragment.php
 create mode 100644 src/Query/From.php
 create mode 100644 src/Query/GroupBy.php
 create mode 100644 src/Query/Having.php
 create mode 100644 src/Query/Insert.php
 create mode 100644 src/Query/Join.php
 create mode 100644 src/Query/Like.php
 create mode 100644 src/Query/Limit.php
 create mode 100644 src/Query/MySQL/Replace.php
 create mode 100644 src/Query/Offset.php
 create mode 100644 src/Query/OrderBy.php
 create mode 100644 src/Query/Select.php
 create mode 100644 src/Query/Where.php
 create mode 100644 src/QueryBuilder.php
 create mode 100644 src/Repository.php
 create mode 100644 src/Ulmus.php

diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..61acd3e
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,18 @@
+{
+    "name": "mcnd/orm",
+    "description": "An hybrid of Active Record and Data Mapper pattern allowing fdirect queries.",
+    "type": "library",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Dave Mc Nicoll",
+            "email": "mcndave@gmail.com"
+        }
+    ],
+    "require": {},
+    "autoload": {
+        "psr-4": {
+            "Ulmus\\": "src/"
+        }
+    }
+}
diff --git a/src/Adapter/AdapterInterface.php b/src/Adapter/AdapterInterface.php
new file mode 100644
index 0000000..06c40e0
--- /dev/null
+++ b/src/Adapter/AdapterInterface.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace Ulmus\Adapter;
+
+use Ulmus\Common\PdoObject;
+
+interface AdapterInterface {
+    public function connect() : PdoObject;
+    public function buildDataSourceName() : string;
+}
diff --git a/src/Adapter/MariaDB.php b/src/Adapter/MariaDB.php
new file mode 100644
index 0000000..5d8495b
--- /dev/null
+++ b/src/Adapter/MariaDB.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace Ulmus\Adapter;
+
+class MariaDB extends MySQL {
+
+}
diff --git a/src/Adapter/MySQL.php b/src/Adapter/MySQL.php
new file mode 100644
index 0000000..4b0c5e0
--- /dev/null
+++ b/src/Adapter/MySQL.php
@@ -0,0 +1,77 @@
+<?php
+
+namespace Ulmus\Adapter;
+
+use Ulmus\Common\PdoObject;
+
+class MySQL implements AdapterInterface {
+
+    public string $hostname;
+    public string $database;
+    public string $username;
+    public string $password;
+    public string $charset;
+    public int $port;
+
+    public function __construct(
+        ?string $hostname = null,
+        ?string $database = null,
+        ?string $username = null,
+        ?string $password = null,
+        ?int $port = null,
+        ?string $charset = null
+    ) {
+        if ($hostname) {
+            $this->hostname = $hostname;
+        }
+
+        if ($database) {
+            $this->database = $database;
+        }
+
+        if ($port) {
+            $this->port = $port;
+        }
+
+        if ($username) {
+            $this->username = $username;
+        }
+
+        if ($password) {
+            $this->password = $password;
+        }
+
+        if ($charset) {
+            $this->charset = $charset;
+        }
+    }
+
+    public function connect() : PdoObject
+    {
+        try {
+            $obj = new PdoObject($this->buildDataSourceName(), $this->username, $this->password);
+        }
+        catch(PDOException $ex){
+            throw $ex;
+        }
+
+        return $obj;
+    }
+
+    public function buildDataSourceName() : string
+    {
+        $parts[] = "host={$this->hostname}";
+        $parts[] = "dbname={$this->database}";
+        $parts[] = "port={$this->port}";
+
+        if ( $this->socket ?? false ) {
+            $parts[] = "socket={$this->socket}";
+        }
+
+        if ( $this->charset ?? false ) {
+            $parts[] = "charset={$this->charset}";
+        }
+
+        return "mysql:" . implode(';', $parts);
+    }
+}
diff --git a/src/Annotation/Annotation.php b/src/Annotation/Annotation.php
new file mode 100644
index 0000000..ac55c70
--- /dev/null
+++ b/src/Annotation/Annotation.php
@@ -0,0 +1,5 @@
+<?php
+
+namespace Ulmus\Annotation;
+
+interface Annotation {}
diff --git a/src/Annotation/AnnotationReader.php b/src/Annotation/AnnotationReader.php
new file mode 100644
index 0000000..76abd00
--- /dev/null
+++ b/src/Annotation/AnnotationReader.php
@@ -0,0 +1,103 @@
+<?php
+
+namespace Ulmus\Annotation;
+
+use Reflector, ReflectionClass, ReflectionProperty, ReflectionMethod;
+
+/**
+ * This class exists while waiting for the official RFC [ https://wiki.php.net/rfc/annotations_v2 ]
+ */
+class AnnotationReader
+{
+    const PHP_TYPES = [ "string", "int", "float", "object", "double", "closure", ];
+
+    public string $class;
+
+    public function __construct($class) {
+        $this->class = $class;
+    }
+
+    public static function fromClass($class) : self
+    {
+        return new static($class);
+    }
+
+    public function getProperty(ReflectionProperty $property)
+    {
+        return $this->parseDocComment($property);
+    }
+
+    public function getClass(ReflectionClass $class)
+    {
+        return $this->parseDocComment($class);
+    }
+
+    public function getMethod(ReflectionMethod $method)
+    {
+        return $this->parseDocComment($method);
+    }
+
+    protected function parseDocComment(Reflector $reflect)
+    {
+        $namespace = $this->getObjectNamespace($reflect);
+        $tags = [];
+
+        foreach(preg_split("/\r\n|\n|\r/", $reflect->getDocComment()) as $line) {
+            $line = ltrim($line, "* \t\/");
+            $line = rtrim($line, "\t ");
+
+            if ( substr($line, 0, 1) === '@' ) {
+                $line = ltrim($line, '@');
+
+                $open = strpos($line, "(");
+                $close = strrpos($line, ")");
+
+                if ( ! in_array(false, [ $open, $close ], true) && ( ++$open !== $close ) ) {
+                    $arguments = substr($line, $open, $close - $open);
+
+                    try {
+                        $tags[] = [
+                            'tag' => substr($line, 0, $open - 1),
+                            'arguments' => eval("namespace $namespace; return [ $arguments ];"),
+                        ];
+                    }
+                    catch(\Throwable $error) {
+                        throw new \InvalidArgumentException("An error occured while parsing annotation from '" . $this->getObjectName($reflect) . "' : @$line -- " . $error->getMessage());
+                    }
+                }
+                else {
+                    $tags[] = [
+                        'tag' => $line,
+                        'arguments' => [],
+                    ];
+                }
+            }
+        }
+
+        return $tags;
+    }
+
+    protected function getObjectName(Reflector $reflect) : string
+    {
+        switch(true) {
+            case $reflect instanceof ReflectionMethod :
+            case $reflect instanceof ReflectionProperty :
+                return $reflect->class . "::" . $reflect->name;
+
+            case $reflect instanceof ReflectionClass :
+                return $reflect->name;
+        }
+    }
+
+    protected function getObjectNamespace(Reflector $reflect) : string
+    {
+        switch(true) {
+            case $reflect instanceof ReflectionMethod :
+            case $reflect instanceof ReflectionProperty :
+                return $reflect->getDeclaringClass()->getNamespaceName();
+
+            case $reflect instanceof ReflectionClass :
+                return $reflect->getNamespaceName();
+        }
+    }
+}
diff --git a/src/Annotation/Classes/Collation.php b/src/Annotation/Classes/Collation.php
new file mode 100644
index 0000000..225c646
--- /dev/null
+++ b/src/Annotation/Classes/Collation.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace Ulmus\Annotation\Classes;
+
+class Collation implements \Ulmus\Annotation\Annotation {
+
+}
diff --git a/src/Annotation/Classes/Method.php b/src/Annotation/Classes/Method.php
new file mode 100644
index 0000000..589aba6
--- /dev/null
+++ b/src/Annotation/Classes/Method.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace Ulmus\Annotation\Classes;
+
+class Function implements \Ulmus\Annotation\Annotation {
+
+}
diff --git a/src/Annotation/Classes/Table.php b/src/Annotation/Classes/Table.php
new file mode 100644
index 0000000..2d10134
--- /dev/null
+++ b/src/Annotation/Classes/Table.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Ulmus\Annotation\Classes;
+
+class Table implements \Ulmus\Annotation\Annotation {
+
+    public string $name;
+
+    public function __construct($name = null)
+    {
+        if ( $name !== null ) {
+            $this->name = $name;
+        }
+    }
+}
diff --git a/src/Annotation/Property/Field.php b/src/Annotation/Property/Field.php
new file mode 100644
index 0000000..c6671a5
--- /dev/null
+++ b/src/Annotation/Property/Field.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Ulmus\Annotation\Property;
+
+class Field implements \Ulmus\Annotation\Annotation {
+
+    public string $type;
+
+    public string $name;
+
+    public int $length;
+
+    public array $attributes = [];
+
+    public bool $nullable = false;
+
+    public function __construct(string $type = null, int $length = null)
+    {
+        switch(true) {
+            case $type !== null:
+                $this->type = $type;
+            break;
+
+            case $length !== null:
+                $this->length = $length;
+            break;
+        }
+    }
+}
diff --git a/src/Annotation/Property/Field/CreatedAt.php b/src/Annotation/Property/Field/CreatedAt.php
new file mode 100644
index 0000000..61b831f
--- /dev/null
+++ b/src/Annotation/Property/Field/CreatedAt.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace Ulmus\Annotation\Property\Field;
+
+class CreatedAt extends \Ulmus\Annotation\Property\Field {
+
+    public function __construct()
+    {
+        $this->nullable = false;
+        $this->type = "timestamp";
+        $this->attributes['default'] = "CURRENT_TIMESTAMP";
+    }
+}
diff --git a/src/Annotation/Property/Field/ForeignKey.php b/src/Annotation/Property/Field/ForeignKey.php
new file mode 100644
index 0000000..f0ae7d0
--- /dev/null
+++ b/src/Annotation/Property/Field/ForeignKey.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Ulmus\Annotation\Property\Field;
+
+/**
+ * Since we need consistancy between the declaration of our ID and FK fields, it
+ * was decided to extend the Id class instead of Field for this case.
+ */
+class ForeignKey extends Id {
+
+    public function __construct()
+    {
+        parent::__construct();
+
+        unset($this->nullable);
+        $this->attributes['primary_key'] = false;
+    }
+
+}
diff --git a/src/Annotation/Property/Field/Id.php b/src/Annotation/Property/Field/Id.php
new file mode 100644
index 0000000..e7f51d2
--- /dev/null
+++ b/src/Annotation/Property/Field/Id.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Ulmus\Annotation\Property\Field;
+
+class Id extends \Ulmus\Annotation\Property\Field {
+
+    public function __construct()
+    {
+        $this->nullable = false;
+        $this->type = "int";
+        $this->attributes['unsigned'] = true;
+        $this->attributes['primary_key'] = true;
+    }
+
+}
diff --git a/src/Annotation/Property/Field/UpdatedAt.php b/src/Annotation/Property/Field/UpdatedAt.php
new file mode 100644
index 0000000..5a33dce
--- /dev/null
+++ b/src/Annotation/Property/Field/UpdatedAt.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Ulmus\Annotation\Property\Field;
+
+class UpdatedAt extends \Ulmus\Annotation\Property\Field {
+
+    public function __construct()
+    {
+        $this->nullable = true;
+        $this->type = "timestamp";
+        $this->attributes['update'] = "CURRENT_TIMESTAMP";
+        $this->attributes['default'] = null;
+    }
+}
diff --git a/src/Annotation/Property/GroupBy.php b/src/Annotation/Property/GroupBy.php
new file mode 100644
index 0000000..6d3eca9
--- /dev/null
+++ b/src/Annotation/Property/GroupBy.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Ulmus\Annotation\Property;
+
+class GroupBy implements \Ulmus\Annotation\Annotation {
+
+    public array $fields = [];
+
+    public function __construct(...$field)
+    {
+        if ( $field ) {
+            $this->fields = $field;
+        }
+    }
+}
diff --git a/src/Annotation/Property/OrderBy.php b/src/Annotation/Property/OrderBy.php
new file mode 100644
index 0000000..0bf9175
--- /dev/null
+++ b/src/Annotation/Property/OrderBy.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Ulmus\Annotation\Property;
+
+class OrderBy implements \Ulmus\Annotation\Annotation {
+
+    public string $field;
+
+    public string $order = "ASC";
+
+    public function __construct(string $field = null, string $order = null)
+    {
+        switch(true) {
+            case $field !== null:
+                $this->field = $field;
+            break;
+
+            case $order !== null:
+                $this->order = $order;
+            break;
+        }
+    }
+}
diff --git a/src/Annotation/Property/Relation.php b/src/Annotation/Property/Relation.php
new file mode 100644
index 0000000..634d8e5
--- /dev/null
+++ b/src/Annotation/Property/Relation.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Ulmus\Annotation\Property;
+
+class Relation implements \Ulmus\Annotation\Annotation {
+
+    public string $type;
+
+    public string $key;
+
+    public string $foreignKey;
+
+    public array $foreignKeys;
+
+    public string $bridge;
+
+    public string $bridgeKey;
+
+    public string $bridgeForeignKey;
+
+    public function __construct(string $type = null)
+    {
+        switch(true) {
+            case $type !== null:
+                $this->type = $type;
+            break;
+        }
+    }
+}
diff --git a/src/Annotation/Property/Where.php b/src/Annotation/Property/Where.php
new file mode 100644
index 0000000..27a457f
--- /dev/null
+++ b/src/Annotation/Property/Where.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Ulmus\Annotation\Property;
+
+class Where implements \Ulmus\Annotation\Annotation {
+
+    public array $comparisons = [];
+
+    public function __construct(...$comparisons)
+    {
+        if ( $comparisons ) {
+            $this->comparisons = $comparisons;
+        }
+    }
+}
diff --git a/src/Common/ArrayObjectTrait.php b/src/Common/ArrayObjectTrait.php
new file mode 100644
index 0000000..172df34
--- /dev/null
+++ b/src/Common/ArrayObjectTrait.php
@@ -0,0 +1,212 @@
+<?php
+
+namespace Ulmus\Common;
+
+trait ArrayObjectTrait {
+
+    protected $arrayobject_pointer   = null;
+
+    protected $arrayobject_container = [];
+
+    protected $arrayobject_changed   = [];
+
+    protected $arrayobject_selected  = false;
+
+    public function count() : int
+    {
+        return count( $this->arrayobject_container() );
+    }
+
+    public function contains($term, $strict = false) : bool
+    {
+        return (array_search($term, $this->arrayobject_container(), $strict) !== false) ;
+    }
+
+    public function &arrayobject_current()
+    {
+        if ( !is_null($this->arrayobject_pointer) ) {
+            $var = &$this->arrayobject_container()[$this->arrayobject_pointer] ?: [];
+            $var || ( $var = [] );
+            return $var;
+        }
+
+        if ( $this->arrayobject_selected !== false ){
+            $ret = &$this->arrayobject_selected ?: [];
+            return $ret;
+        }
+
+        # Restoring integrity of container since it could be nullified
+        if ( ! is_array($this->arrayobject_container()) ) {
+            $this->arrayobject_container([]);
+        }
+
+        return $this->arrayobject_container();
+    }
+
+    public function offsetSet($offset, $value, $changed = null)
+    {
+        if ( $changed && (!isset($this->arrayobject_current()[$offset]) || ($this->arrayobject_current()[$offset] !== $value) ) ) {
+            $this->arrayobject_changed($offset, true);
+        }
+
+        return is_null($offset) ? $this->arrayobject_current()[] = $value : $this->arrayobject_current()[$offset] = $value;
+    }
+
+    public function arrayobject_set_pointer($pointer)
+    {
+        # $pointer could nullify obj pointer
+        if ( $this->arrayobject_pointer = $pointer ) {
+            # Creating dataset whenever we have a new one
+            if ( ! isset($this->arrayobject_container()[$this->arrayobject_pointer]) ) {
+                $this->arrayobject_container()[$this->arrayobject_pointer] = [];
+                $this->arrayobject_changed[$this->arrayobject_pointer]   = [];
+            }
+        }
+
+        return $this;
+    }
+
+    public function arrayobject_select($selection, $purge = true)
+    {
+        if ( is_bool($selection) ) {
+            return $this->arrayobject_selected = $selection;
+        }
+
+        $purge && ( $this->arrayobject_selected = [] );
+
+        foreach($selection as $pointer) {
+            $this->arrayobject_selected[$pointer] = &$this->arrayobject_container[$pointer];
+        }
+
+        return true;
+    }
+
+    public function arrayobject_exist($pointer) : bool
+    {
+        return isset( $this->arrayobject_container()[$pointer] );
+    }
+
+    public function arrayobject_flush_changed() {
+        ! is_null($this->arrayobject_pointer) ?
+            $this->arrayobject_changed[$this->arrayobject_pointer] = []
+        :
+            $this->arrayobject_changed = []
+        ;
+    }
+
+    public function arrayobject_changed($offset = null, $set = null) {
+        if ($offset) {
+            if ($set !== null) {
+                ! is_null($this->arrayobject_pointer) ?
+                    ( $this->arrayobject_changed[$this->arrayobject_pointer][$offset] = $set )
+                :
+                    ( $this->arrayobject_changed[$offset] = $set );
+            }
+
+            return !is_null($this->arrayobject_pointer) ? $this->arrayobject_changed[$this->arrayobject_pointer][$offset] : $this->arrayobject_changed[$offset];
+        }
+
+        return array_keys( !is_null($this->arrayobject_pointer)
+        ?   $this->arrayobject_changed[$this->arrayobject_pointer] ?? []
+        :   $this->arrayobject_changed) ?? [];
+    }
+
+    public function arrayobject_remove($pointer) {
+        if ( isset($this->arrayobject_container()[$pointer]) ) {
+            unset( $this->arrayobject_container()[$pointer], $this->arrayobject_changed[$pointer]);
+        }
+    }
+
+    public function arrayobject_iterate($callback) {
+        if ( $callback && is_callable($callback) ) {
+            $pointer = $this->arrayobject_pointer;
+
+            foreach($this->arrayobject_container() as $key => $value) {
+                $this->arrayobject_set_pointer($key);
+                $callback($key, $this);
+            }
+
+            $this->arrayobject_set_pointer($pointer);
+        }
+
+        return $this;
+    }
+
+
+    public function offsetGet($offset)
+    {
+        if ( !is_null($this->arrayobject_pointer) ) {
+            return isset($this->arrayobject_container()[$this->arrayobject_pointer][$offset]) ? $this->arrayobject_container()[$this->arrayobject_pointer][$offset] : null;
+        }
+        else {
+            return isset($this->arrayobject_container()[$offset]) ? $this->arrayobject_container()[$offset] : null;
+        }
+    }
+
+    public function offsetExists($offset) : bool
+    {
+        return array_key_exists($offset, $this->arrayobject_current() );
+    }
+
+    public function offsetUnset($offset)
+    {
+        if ( !is_null($this->arrayobject_pointer)) {
+            unset($this->arrayobject_container()[$this->arrayobject_pointer][$offset]);
+        }
+        else {
+            unset($this->arrayobject_container()[$offset]) ;
+        }
+    }
+
+    public function arrayobject_sort($field, $order = 'ASC')
+    {
+        Arrayobj::order_by($this->arrayobject_container(), $field);
+        $order === 'DESC' && array_reverse($this->arrayobject_current());
+    }
+
+    public function rewind()
+    {
+        reset( $this->arrayobject_container() );
+
+        # Rewinding will also reset the pointer
+        $this->arrayobject_set_pointer(key($this->arrayobject_container()));
+
+        return $this;
+    }
+
+    public function current()
+    {
+        return $this->arrayobject_set_pointer( $this->key() );
+    }
+
+    public function key()
+    {
+        $var = key( $this->arrayobject_container() );
+        return $var;
+    }
+
+    public function next()
+    {
+        $var = next( $this->arrayobject_container() );
+        return $var;
+    }
+
+    public function valid() : bool
+    {
+        $key = $this->key();
+        return ( $key !== NULL ) && ( $key !== FALSE );
+    }
+
+    protected function &arrayobject_container($set = null)
+    {
+        if ( $set !== null ) {
+            $this->arrayobject_container = $set;
+        }
+
+        if (  $this->arrayobject_selected !== false ) {
+            return $this->arrayobject_selected;
+        }
+
+        return $this->arrayobject_container;
+    }
+}
diff --git a/src/Common/EntityField.php b/src/Common/EntityField.php
new file mode 100644
index 0000000..a15df85
--- /dev/null
+++ b/src/Common/EntityField.php
@@ -0,0 +1,56 @@
+<?php
+
+namespace Ulmus\Common;
+
+use Ulmus\Ulmus;
+
+class EntityField
+{
+    public string $name;
+
+    public string $entityClass;
+
+    public string $alias;
+
+    protected EntityResolver $entityResolver;
+
+    public function __construct(string $entityClass, string $name, string $alias)
+    {
+        $this->entityClass = $entityClass;
+        $this->name = $name;
+        $this->alias = $alias;
+        $this->entityResolver = Ulmus::resolveEntity(static::class);
+    }
+
+    public function name($useAlias = true) : string
+    {
+        # Must use REFLECTION before throwing this value.
+        # Should first check if it's a relation field, and if it is,
+        # it's real key must be returned (PK usually)
+        return $useAlias ? "{$this->alias}.`{$this->name}`" : $this->name;
+    }
+
+    public static function isScalarType($type) : bool
+    {
+        switch($type) {
+            case 'int':
+            case 'bool':
+            case 'string':
+            case 'float':
+            case 'double':
+                return true;
+        }
+
+        return false;
+    }
+
+    public static function isObjectType($type) : bool
+    {
+        return strpos($type, "\\") !== false;
+    }
+
+    public function __toString() : string
+    {
+        return $this->name();
+    }
+}
diff --git a/src/Common/EntityResolver.php b/src/Common/EntityResolver.php
new file mode 100644
index 0000000..ae45936
--- /dev/null
+++ b/src/Common/EntityResolver.php
@@ -0,0 +1,185 @@
+<?php
+
+namespace Ulmus\Common;
+
+use Ulmus\Annotation\Annotation,
+    Ulmus\Annotation\Classes\Table,
+    Ulmus\Annotation\Property\Field;
+
+class EntityResolver {
+
+    const KEY_ENTITY_NAME = 01;
+    const KEY_COLUMN_NAME = 02;
+
+    public string $entityClass;
+
+    public array $uses;
+
+    public array $class;
+
+    public array $properties;
+
+    public array $methods;
+
+    public function __construct(string $entityClass)
+    {
+        $this->entityClass = $entityClass;
+
+        list($this->uses, $this->class, $this->methods, $this->properties) = array_values(
+            ObjectReflection::fromClass($entityClass)->read()
+        );
+
+        $this->resolveAnnotations();
+    }
+
+    public function field($name, $fieldKey = self::KEY_ENTITY_NAME) : ? array
+    {
+        try{
+            return $this->fieldList($fieldKey)[$name];
+        }
+        catch(\Throwable $e) {
+            throw new \InvalidArgumentException("Can't find entity field's column named `$name` from entity {$this->entityClass}");
+        }
+    }
+
+    public function fieldList($fieldKey = self::KEY_ENTITY_NAME) : array
+    {
+        $fieldList = [];
+
+        foreach($this->properties as $item) {
+            foreach($item['tags'] ?? [] as $tag) {
+                if ( $tag['object'] instanceof Field ) {
+                    switch($fieldKey) {
+                        case static::KEY_ENTITY_NAME:
+                            $key = $item['name'];
+                        break;
+
+                        case static::KEY_COLUMN_NAME:
+                            $key = $tag['object']->name ?? $item['name'];
+                        break;
+
+                        default:
+                            throw new \InvalidArgumentException("Given `fieldKey` is unknown to the EntityResolver");
+                    }
+
+                    $fieldList[$key] = $item;
+                    break;
+                }
+            }
+        }
+
+        return $fieldList;
+    }
+
+    public function tableName() : string
+    {
+        if ( null === $table = $this->getAnnotationFromClassname( Table::class ) ) {
+            throw new \LogicException("Your entity {$this->entityClass} seems to be missing a @Table() annotation");
+        }
+
+        if ( $table->name === "" ) {
+            throw new \ArgumentCountError("Your entity {$this->entityClass} seems to be missing a `name` argument for your @Table() annotation");
+        }
+
+        return $table->name;
+    }
+
+    public function primaryKeys() : array
+    {
+
+    }
+
+    /**
+     * Transform an annotation into it's object's counterpart
+     */
+    public function getAnnotationFromClassname(string $className) : ? object
+    {
+        if ( $name = $this->uses[$className] ?? false) {
+
+            foreach($this->class['tags'] as $item) {
+                if ( $item['tag'] === $name ) {
+                    return $this->instanciateAnnotationObject($item);
+                }
+
+                foreach($this->properties as $item) {
+                    foreach($item['tags'] as $item) {
+                        if ( $item['tag'] === $name ) {
+                            return $this->instanciateAnnotationObject($item);
+                        }
+                    }
+                }
+
+                foreach($this->methods as $item) {
+                    foreach($item['tags'] as $item) {
+                        if ( $item['tag'] === $name ) {
+                            return $this->instanciateAnnotationObject($item);
+                        }
+                    }
+                }
+
+            }
+
+            throw new \TypeError("Annotation `$className` could not be found within your object `{$this->entityClass}`");
+        }
+        else {
+            throw new \InvalidArgumentException("Class `$className` was not found within {$this->entityClass} uses statement (or it's children / traits)");
+        }
+
+        return null;
+    }
+
+    public function instanciateAnnotationObject(array $tagDefinition) : Annotation
+    {
+        $arguments = $this->extractArguments($tagDefinition['arguments']);
+
+        if ( false === $class = array_search($tagDefinition['tag'], $this->uses) ) {
+            throw new \InvalidArgumentException("Annotation class `{$tagDefinition['tag']}` was not found within {$this->entityClass} uses statement (or it's children / traits)");
+        }
+
+        $obj = new $class(... $arguments['constructor']);
+
+        foreach($arguments['setter'] as $key => $value) {
+            $obj->$key = $value;
+        }
+
+        return $obj;
+    }
+
+    /**
+     * Extracts arguments from an Annotation definition, easing object's declaration.
+     */
+    protected function extractArguments(array $arguments) : array
+    {
+        $list = [
+            'setter' => [],
+            'constructor' => [],
+        ];
+
+        ksort($arguments);
+
+        foreach($arguments as $key => $value) {
+            $list[ is_int($key) ? 'constructor' : 'setter' ][$key] = $value;
+        }
+
+        return $list;
+    }
+
+    protected function resolveAnnotations()
+    {
+        foreach($this->class['tags'] as &$tag) {
+            $tag['object'] = $this->instanciateAnnotationObject($tag);
+        }
+
+        foreach($this->properties as &$property) {
+            foreach($property['tags'] as &$tag){
+                $tag['object'] = $this->instanciateAnnotationObject($tag);
+            }
+        }
+
+        foreach($this->methods as &$method) {
+            foreach($method['tags'] as &$tag){
+                $tag['object'] = $this->instanciateAnnotationObject($tag);
+            }
+        }
+    }
+}
diff --git a/src/Common/ObjectReflection.php b/src/Common/ObjectReflection.php
new file mode 100644
index 0000000..77de42d
--- /dev/null
+++ b/src/Common/ObjectReflection.php
@@ -0,0 +1,273 @@
+<?php
+
+namespace Ulmus\Common;
+
+use Ulmus\Ulmus;
+
+use Ulmus\Annotation\AnnotationReader;
+
+use Reflector, ReflectionClass, ReflectionProperty, ReflectionMethod;
+
+class ObjectReflection {
+
+    public AnnotationReader $annotationReader;
+
+    public function __construct($class, AnnotationReader $annotationReader = null) {
+        $this->classReflection = $class instanceof ReflectionClass ? $class : new ReflectionClass($class);
+        $this->annotationReader = $annotationReader ?: AnnotationReader::fromClass($class);
+    }
+
+    public static function fromClass($class) : self
+    {
+        return new static($class);
+    }
+
+    public function read() : array
+    {
+        return [
+            'uses'     => $this->gatherUses(true),
+            'class'    => $this->gatherClass(true),
+            'method'   => $this->gatherMethods(true),
+            'property' => $this->gatherProperties(true),
+        ];
+    }
+
+    public function gatherUses(bool $full = true) : array
+    {
+        $list = [];
+
+        if ( $full ) {
+            if ( $parentClass = $this->classReflection->getParentClass() ) {
+                $list = static::fromClass($parentClass)->gatherUses(true);
+            }
+
+            foreach($this->classReflection->getTraits() as $trait) {
+                $list = array_merge($list, static::fromClass($trait)->gatherUses(true));
+            }
+        }
+
+        return array_merge($this->getUsesStatements(), $list);
+    }
+
+    public function gatherClass(bool $full = true) : array
+    {
+        $class = [];
+
+        if ( $full ) {
+            if ( $parentClass = $this->classReflection->getParentClass() ) {
+                $class = static::fromClass($parentClass)->gatherClass(true);
+            }
+
+            $itemName = function($item) {
+                return $item->getName();
+            };
+        }
+
+        return [
+            'tags' => array_merge($class, $this->annotationReader->getClass($this->classReflection))
+        ] + ( ! $full ? [] : [
+            'traits' => array_map($itemName, $this->classReflection->getTraits()),
+            'interfaces' => array_map($itemName, $this->classReflection->getInterfaces()),
+        ]);
+    }
+
+    public function gatherProperties(bool $full = true, int $filter =
+        ReflectionProperty::IS_PUBLIC    |
+        ReflectionProperty::IS_PROTECTED |
+        ReflectionProperty::IS_PRIVATE
+    ) : array
+    {
+        $properties = [];
+        $defaultValues = $this->classReflection->getDefaultProperties();
+
+        if ( $full ) {
+            if ( $parentClass = $this->classReflection->getParentClass() ) {
+                $properties = static::fromClass($parentClass)->gatherProperties($full, $filter);
+            }
+        }
+
+        $properties = array_merge($properties, $this->classReflection->getProperties($filter));
+
+        $list = [];
+
+        foreach($properties as $property) {
+            $current = [
+                'name' => $property->getName()
+            ];
+
+            # Default value can be 'null', so isset() it not suitable here
+            if ( array_key_exists($current['name'], $defaultValues) ) {
+                $current['value'] = $defaultValues[ $current['name'] ];
+            }
+
+            if ( $property->hasType() ) {
+                $current['type'] = $property->getType()->getName();
+                $current['nullable'] = $property->getType()->allowsNull();
+            }
+
+            $current['tags'] = $this->annotationReader->getProperty($property);
+
+            if ( $this->ignoreElementAnnotation($current['tags']) ) {
+                continue;
+            }
+
+            $list[ $current['name'] ] = $current;
+        }
+
+        return $list;
+    }
+
+    public function gatherMethods(bool $full = true, int $filter =
+        ReflectionMethod::IS_PUBLIC    |
+        ReflectionMethod::IS_PROTECTED |
+        ReflectionMethod::IS_PRIVATE   |
+        ReflectionMethod::IS_STATIC
+    ) : array
+    {
+        $methods = [];
+
+        if ( $full ) {
+            if ( $parentClass = $this->classReflection->getParentClass() ) {
+                $methods = static::fromClass($parentClass)->gatherMethods($full, $filter);
+            }
+        }
+
+        $methods = array_merge($methods, $this->classReflection->getMethods($filter));
+
+        $list = [];
+
+        foreach($methods as $method) {
+            $parameters = [];
+
+            foreach($method->getParameters() as $parameter) {
+                $parameters[$parameter->getName()] = [
+                    'null' => $parameter->allowsNull(),
+                    'position' => $parameter->getPosition(),
+                    'type' => $parameter->hasType() ? $parameter->getType()->getName() : false,
+                    'array' => $parameter->isArray(),
+                    'callable' => $parameter->isCallable(),
+                    'optional' => $parameter->isOptional(),
+                    'byReference' => $parameter->isPassedByReference(),
+                ];
+            }
+
+            $current = [
+                'name' => $method->getName(),
+                'type' => $method->hasReturnType() ? $method->getReturnType()->getName() : false,
+                'constructor' => $method->isConstructor(),
+                'destructor' => $method->isDestructor(),
+                'parameters' => $parameters,
+            ];
+
+            $current['tags'] = $this->annotationReader->getMethod($method);
+
+            if ( $this->ignoreElementAnnotation($current['tags']) ) {
+                continue;
+            }
+
+            $list[ $current['name'] ] = $current;
+        }
+
+        return $list;
+    }
+
+    protected function ignoreElementAnnotation($tags) : bool
+    {
+        return in_array('IGNORE', array_map('strtoupper', array_column($tags, 'tag') ));
+    }
+
+
+    protected function readCode() : string
+    {
+        static $code = [];
+        $fileName = $this->classReflection->getFilename();
+        return $code[$fileName] ?? $code[$fileName] = file_get_contents($fileName);
+    }
+
+    protected function getUsesStatements() : array
+    {
+        $uses = [];
+        $tokens = token_get_all( $c = $this->readCode() );
+
+        while ( $token = array_shift($tokens) ) {
+
+            if ( is_array($token) ) {
+                list($token, $value) = $token;
+            }
+
+            switch ($token) {
+                case T_CLASS:
+                case T_TRAIT:
+                case T_INTERFACE:
+                    break 2;
+
+    			case T_USE:
+                    $isUse = true;
+				break;
+
+                case T_NS_SEPARATOR:
+                    $isNamespace = $isUse;
+                break;
+
+                case T_STRING:
+                    if ( $isNamespace && $latestString ) {
+                        $statement[] = $latestString;
+                    }
+
+                    $latestString = $value;
+                break;
+
+                case T_AS:
+                    # My\Name\Space\aClassHere `as` ClassAlias;
+                    $replacedClass = implode("\\", array_merge($statement, [ $latestString ]));
+                    $latestString = null;
+                break;
+
+                case T_WHITESPACE:
+                case T_COMMENT:
+                case T_DOC_COMMENT:
+                break;
+
+                case '{':
+                    # opening a sub-namespace -> \My\Name\Space\`{`OneItem, AnotherItem}
+                    if ( $isNamespace ) {
+                        $inNamespace = true;
+                    }
+                break;
+
+                case ';';
+                case ',':
+                case '}':
+                    if ( $isUse ) {
+                        if ( $replacedClass ) {
+                            $uses[$replacedClass] = $latestString;
+                            $replacedClass = "";
+                        }
+                        elseif ( $latestString ) {
+                            $uses[implode("\\", array_merge($statement, [ $latestString ]))] = $latestString;
+                        }
+                    }
+
+                    if ( $inNamespace ) {
+                        $latestString = "";
+
+                        # \My\Name\Space\{OneItem, AnotherItem`}` <- closing a sub-namespace
+                        if ( $token !== "}" ) {
+                            break;
+                        }
+                    }
+
+                case T_OPEN_TAG:
+                default:
+                    $statement = [];
+                    $latestString = "";
+                    $replacedClass = null;
+                    $isNamespace = $inNamespace = false;
+                    $isUse = ( $isUse ?? false ) && ( $token === ',' );
+                break;
+            }
+		}
+
+        return $uses;
+    }
+}
diff --git a/src/Common/PdoObject.php b/src/Common/PdoObject.php
new file mode 100644
index 0000000..cc1274c
--- /dev/null
+++ b/src/Common/PdoObject.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace Ulmus\Common;
+
+use PDO, PDOStatement;
+
+class PdoObject extends PDO {
+
+    public function select(string $sql, array $parameters = []) : PDOStatement
+    {
+        try {
+            if ( $statement = $this->prepare($sql) ) {
+                $statement = $this->execute($statement, $parameters, true);
+                $statement->setFetchMode(\PDO::FETCH_ASSOC);
+                return $statement;
+            }
+        } catch (\PDOException $e) { throw $e; }
+    }
+
+    public function execute(PDOStatement $statement, array $parameters = [], bool $commit = true) : ? PDOStatement
+    {
+       try {
+           if (! $this->inTransaction() ) {
+               $this->beginTransaction();
+           }
+           
+           if ( empty($parameters) ? $statement->execute() : $statement->execute($parameters) ) {
+              # if ( $commit ) {
+                   $this->commit();
+              # }
+
+               return $statement;
+           }
+           else {
+               throw new PDOException('Could not begin transaction or given statement is invalid.');
+           }
+       } catch (\PDOException $e) {
+           $this->rollback();
+           throw $e;
+       }
+
+       return null;
+   }
+
+}
diff --git a/src/Common/Sql.php b/src/Common/Sql.php
new file mode 100644
index 0000000..ce79fa4
--- /dev/null
+++ b/src/Common/Sql.php
@@ -0,0 +1,67 @@
+<?php
+
+namespace Ulmus\Common;
+
+abstract class Sql {
+
+    public static function function($name, ...$arguments)
+    {
+        return new class($name, ...$arguments) {
+
+            protected string $as = "";
+
+            protected string $name;
+
+            protected array $arguments;
+
+            public function __construct(string $name, ...$arguments) {
+                $this->name = $name;
+                $this->arguments = $arguments;
+                $this->parseArguments();
+            }
+
+            public function __toString() {
+                return implode(' ', array_filter([
+                    "{$this->name}(" . implode(", ", $this->arguments) . ")",
+                    $this->as ? "AS {$this->as}" : false,
+                ]));
+            }
+
+            public function as($fieldName) {
+                $this->as = $fieldName;
+                return $this;
+            }
+
+            protected function parseArguments() {
+                foreach($this->arguments as &$item) {
+                    $item = Sql::escape($item);
+                }
+            }
+        };
+    }
+
+    public static function escape($value)
+    {
+        switch(true) {
+            case is_object($value):
+                # @TODO Make sure the object is a Field
+                return (string) $value;
+            break;
+
+            case is_string($value):
+                $value = "\"$value\"";
+            break;
+
+            case is_null($value):
+                $value = "NULL";
+            break;
+        }
+
+        return $value;
+    }
+
+    public static function parameter($value) : string
+    {
+        
+    }
+}
diff --git a/src/ConnectionAdapter.php b/src/ConnectionAdapter.php
new file mode 100644
index 0000000..cc4fe67
--- /dev/null
+++ b/src/ConnectionAdapter.php
@@ -0,0 +1,87 @@
+<?php
+
+namespace Ulmus;
+
+use Ulmus\Adapter\AdapterInterface;
+
+use Ulmus\Common\PdoObject;
+
+class ConnectionAdapter
+{
+    public string $name;
+
+    public array $configuration;
+
+    protected AdapterInterface $adapter;
+
+    public PdoObject $pdo;
+
+    public function __construct(string $name = "default", array $configuration = [])
+    {
+        $this->name = $name;
+        $this->configuration = $configuration;
+
+        if ( $name === "default" ) {
+            Ulmus::$defaultAdapter = $this;
+        }
+    }
+
+    public function resolveConfiguration()
+    {
+        $connection = $this->configuration['connections'][$this->name] ?? [];
+
+        if ( $adapterName = $connection['adapter'] ?? false ) {
+            $this->adapter = $this->instanciateAdapter($adapterName);
+        }
+        else {
+            throw new \InvalidArgumentException("Adapter not found within your configuration array.");
+        }
+
+        if ( false === $this->adapter->hostname = $connection['host'] ?? false ) {
+            throw new \InvalidArgumentException("Your `host` name is missing from your configuration array");
+        }
+
+        if ( false === $this->adapter->port = $connection['port'] ?? false ) {
+            throw new \InvalidArgumentException("Your `port` number is missing from your configuration array");
+        }
+
+        if ( false === $this->adapter->database = $connection['database'] ?? false ) {
+            throw new \InvalidArgumentException("Your `database` name is missing from your configuration array");
+        }
+
+        if ( false === $this->adapter->username = $connection['username'] ?? false ) {
+            throw new \InvalidArgumentException("Your `username` is missing from your configuration array");
+        }
+
+        if ( false === $this->adapter->password = $connection['password'] ?? false ) {
+            throw new \InvalidArgumentException("Your `password` is missing from your configuration array");
+        }
+    }
+
+    /**
+     * Connect the adapter
+     * @return self
+     */
+    public function connect()
+    {
+        $this->pdo = $this->adapter->connect();
+        $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
+        $this->pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false);
+        $this->pdo->setAttribute(\PDO::ATTR_AUTOCOMMIT, false);
+        $this->pdo->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC);
+        $this->pdo->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
+
+        return $this;
+    }
+
+    /**
+     * Instanciate an adapter which interact with the data source
+     * @param  string $name An Ulmus adapter or full class name implementing AdapterInterface
+     * @return AdapterInterface
+     */
+    protected function instanciateAdapter($name)
+    {
+        $class = substr($name, 0, 2) === "\\" ? $name : "\\Ulmus\\Adapter\\$name";
+        return new $class();
+    }
+}
diff --git a/src/EntityCollection.php b/src/EntityCollection.php
new file mode 100644
index 0000000..5b6b6e6
--- /dev/null
+++ b/src/EntityCollection.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace Ulmus;
+
+class EntityCollection extends \ArrayObject
+{
+    use Common\ArrayObjectTrait;
+}
diff --git a/src/EntityTrait.php b/src/EntityTrait.php
new file mode 100644
index 0000000..2b0d33b
--- /dev/null
+++ b/src/EntityTrait.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace Ulmus;
+
+use Ulmus\Repository,
+    Ulmus\Common\EntityResolver,
+    Ulmus\Common\EntityField;
+
+use Ulmus\Annotation\Classes\{ Method, Table, Collation as Test, };
+use Ulmus\Annotation\Property\{ Field, Relation, OrderBy, Where, };
+use Ulmus\Annotation\Property\Field\{ Id, ForeignKey, CreatedAt, UpdatedAt, };
+
+trait EntityTrait {
+
+    public function entityFillFromDataset($dataset) : self
+    {
+        $fields = Ulmus::resolveEntity(static::class);
+
+        foreach($dataset as $key => $value) {
+            if ( null === $field = $fields->field($key, EntityResolver::KEY_COLUMN_NAME) ?? null ) {
+                throw new \Exception("Field `$key` can not be found within your entity ".static::class);
+            }
+
+            if ( is_null($value) ) {
+                $this->{$field['name']} = null;
+            }
+            elseif ( $field['type'] === 'array' ) {
+                $this->{$field['name']} = substr($value, 0, 1) === "a" ? unserialize($value) : json_decode($value, true);
+            }
+            elseif ( EntityField::isScalarType($field['type']) ) {
+                $this->{$field['name']} = $value;
+            }
+            elseif ( EntityField::isObjectType($field['type']) ) {
+                $this->{$field['name']} = new $field['type']();
+            }
+        }
+
+        return $this;
+    }
+
+    /**
+     * @Ignore
+     */
+    public static function repository() : Repository
+    {
+        return Ulmus::repository(static::class);
+    }
+
+    /**
+     * @Ignore
+     */
+    public static function queryBuilder() : QueryBuilder
+    {
+        return Ulmus::queryBuilder(static::class);
+    }
+
+    /**
+     * @Ignore
+     */
+    public static function field($name, ? string $alias = null)
+    {
+        return new EntityField(static::class, $name, $alias ?: Repository::DEFAULT_ALIAS);
+    }
+
+    /**
+     * @Ignore
+     */
+    public static function fields(...$fields)
+    {
+        return implode(', ', array_map(function($name) {
+            return static::field($name);
+        }, $fields));
+    }
+
+    /**
+     * @Ignore
+     */
+    public static function table()
+    {
+        return "REFLECT TABLE";
+    }
+}
diff --git a/src/Modeler/Field.php b/src/Modeler/Field.php
new file mode 100644
index 0000000..e69de29
diff --git a/src/Modeler/Query.php b/src/Modeler/Query.php
new file mode 100644
index 0000000..0db7442
--- /dev/null
+++ b/src/Modeler/Query.php
@@ -0,0 +1,404 @@
+<?php namespace Alive\Storage\Sql;
+
+use Alive\{
+    constructor,
+    Arrayobj
+};
+
+class QueryBuilder {
+    protected $fields;
+    protected $where;
+    protected $order_by;
+    protected $group_by;
+    protected $limit;
+
+    public static $syntax = [
+        'against'  => 'AGAINST',
+        'and'      => 'AND',
+        'as'       => 'AS',
+        'charset'  => 'CHARACTER SET',
+        'collate'  => 'COLLATE',
+        'create'   => 'CREATE',
+        'database' => 'DATABASE',
+        'delete'   => 'DELETE FROM',
+        'distinct' => 'DISTINCT',
+        'drop'     => 'DROP',
+        'engine'   => 'ENGINE',
+        '!exist'   => 'IF NOT EXISTS',
+        'exist'    => 'IF EXISTS',
+        'explain'  => 'EXPLAIN',
+        'from'     => 'FROM',
+        'grant'    => 'GRANT',
+        'grant_option' => 'GRANT OPTION',
+        'group_by' => 'GROUP BY',
+        'having'   => 'HAVING',
+        'in'       => 'IN',
+        'insert'   => 'INSERT INTO',
+        'join'     => 'JOIN',
+        'join-left' => 'LEFT',
+        'join-right' => 'RIGHT',
+        'join-inner' => 'INNER',
+        'join-full'  => 'FULL',
+        'join-self'  => 'SELF',
+        #'join-outer' => 'OUTER',
+        'join-cross' => 'CROSS',
+        'like'     => 'LIKE',
+        'limit'    => 'LIMIT',
+        'match'    => 'MATCH',
+        'not_in'   => 'NOT IN',
+        'on'       => 'ON',
+        'on_table' => 'ON TABLE',
+        'or'       => 'OR',
+        'order_by' => 'ORDER BY',
+        'offset'   => 'OFFSET',
+        'revoke'   => 'REVOKE',
+        'select'   => 'SELECT',
+        'set'      => 'SET',
+        'table'    => 'TABLE',
+        'table_charset' => 'DEFAULT CHARSET',
+        'to'       => 'TO',
+        'update'   => 'UPDATE',
+        'values'   => 'VALUES',
+        'where'    => 'WHERE'
+    ];
+
+    static $escape_char = '`';
+
+    protected $compiled = [];
+
+    public static function select($param) {
+        $param = Arrayobj::make($param);
+
+        return static::prepare_array([
+            $param->if_has('explain'  , static::$syntax['explain']),
+            static::$syntax['select'],
+            $param->if_has('distinct' , static::$syntax['distinct']),
+            static::group_fields($param['fields'] ?: '*'),
+            static::$syntax['from'],
+            static::full_tablename($param),
+            static::prepare_join($param['join']),
+            static::prepare_where($param['where'], false, $param->ternary('escaped', true)),
+            $param->if_has('group_by' , static::prepare_group($param['group_by'], $param['alias'] ?? null)),
+            $param->if_has('having'   , static::$syntax['having']." {$param['having']}"),
+            /*  @todo  UNION | INTERSECT | EXCEPT  GOES HERE !*/
+            $param->if_has('order_by' , static::prepare_order($param['order_by'], $param['alias'] ?? null)),
+            static::prepare_limit($param)
+        ]);
+    }
+
+    /**
+     * This function will translate parameters into a "create database" or "create table", depending
+     * on given param.
+     *
+     * @param array $param 'subject': table or database
+     *
+     * @return Type    Description
+     */
+    public static function create($param) {
+        $param = Arrayobj::make($param);
+        return strtolower( $param['subject'] ) === 'table' ? static::create_table($param) : static::create_database($param);
+    }
+
+    public static function create_table($param) {
+        $param = is_array($param) ? Arrayobj::make($param) : $param;
+
+        return static::prepare_array([
+            static::$syntax['create'],
+            static::$syntax['table'],
+            $param->if_has('!exist', static::$syntax['!exist']),
+            static::full_tablename($param),
+            static::group_create_fields($param->mandatory('fields'), true),
+            $param->if_has('collation' , static::$syntax['collate']." {$param['collation']}" )
+        ]);
+    }
+
+    public static function create_database($param) {
+        $param = is_array($param) ? Arrayobj::make($param) : $param;
+
+        return static::prepare_array([
+            static::$syntax['create'],
+            static::$syntax['database'],
+            $param->if_has('!exist', static::$syntax['!exist']),
+            static::escape( $param->mandatory('database') )
+        ]);
+    }
+
+    public static function insert($param) {
+        $param = Arrayobj::make($param);
+
+        $field_label  = static::group_fields( $param->mandatory('fields'), true, true );
+        $field_values = static::group_values( $param->mandatory('values'), $param['escaped'] ?: false );
+
+        return static::prepare_array([
+            static::$syntax['insert'],
+            static::full_tablename($param),
+            $field_label,
+            static::$syntax['values'],
+            $field_values
+        ]);
+    }
+
+    public static function grant($param) {
+        $param = Arrayobj::make($param);
+
+        $field_label = static::group_fields( $param->mandatory('privileges') );
+        $users = static::group_fields( $param->mandatory('users') );
+
+        return static::prepare_array([
+            static::$syntax['grant'],
+            $field_label,
+            static::$syntax['on_table'],
+            static::full_tablename($param),
+            static::$syntax['to'],
+            $users,
+            $param->if_has('grant_option', static::$syntax['grant_option'])
+        ]);
+    }
+
+    public static function delete($param) {
+        $param = Arrayobj::make($param);
+
+        return static::prepare_array([
+            static::$syntax['delete'],
+            static::full_tablename($param),
+            static::prepare_where($param['where'], false, $param->ternary('escaped', true)),
+            static::prepare_order($param),
+            static::prepare_limit($param)
+        ]);
+    }
+
+    public static function update($param) {
+        $param = Arrayobj::make($param);
+
+        $fields = static::group_values_and_fields($param->mandatory('fields'), $param->mandatory('values'));
+
+        return static::prepare_array([
+            static::$syntax['update'],
+            static::full_tablename($param),
+            static::$syntax['set'],
+            $fields,
+            static::prepare_where($param['where'])
+        ]);
+    }
+
+    public static function drop($param) {
+        $param = Arrayobj::make($param);
+
+        return static::prepare_array([
+            static::$syntax['drop'],
+            $param->exist('table_name') ? static::$syntax['table']." ".static::full_tablename($param) : static::$syntax['database']." ".static::escape($param->mandatory('database'))
+        ]);
+    }
+
+    public static function full_tablename($param) {
+        is_array($param) && ($param = Arrayobj::make($param));
+        return $param->if_has('database', static::escape($param['database']).".") . static::escape($param->mandatory('table_name')) . $param->if_has('alias', " ".static::$syntax['as']." " . $param['alias']);
+    }
+
+    public static function group_fields($fields, $enclose = false, $escape = false) {
+        if (is_array($fields)) {
+            return ($enclose ? "(" : "") .implode(', ', $escape ? array_map(function($item){ return static::escape($item); }, $fields) : $fields).($enclose ? ")" : "");
+        }
+        else {
+            return $escape ? static::escape($fields) : $fields;
+        }
+    }
+
+    public static function group_create_fields($fields, $enclose = false) {
+        if (is_array($fields)) {
+            $retval = [];
+
+            foreach($fields as $key => $value) {
+                $retval[] = static::escape($key)." ".$value;
+            }
+
+            return ($enclose ? "(" : "") .implode(', ', $retval).($enclose ? ")" : "");
+        }
+        else {
+            return $fields;
+        }
+    }
+
+    public static function group_values($values, $escaped = false) {
+        $tmp = array_pop($values);
+        array_push($values, $tmp);
+
+        # Are we dealing with an array of values ?
+        if ( is_array($tmp) ) {
+            $retval = [];
+
+            foreach($values as $item) {
+                $retval[] = implode(', ', $escaped ? $item : static::escape_values($item) );
+            }
+
+            return "(".implode('), (', $retval).")";
+        }
+        else {
+            return "(".implode(', ', $escaped ? $values : static::escape_values($values)).")";
+        }
+    }
+
+    public static function escape_values($values) {
+        $type_function = function(& $item) {
+
+            switch( $t = gettype($item) ) {
+                case "boolean":
+                    $item = $item ? 1 : 0;
+                    break;
+
+                case "double":
+                case "integer":
+                    break;
+
+                case "NULL":
+                    $item = "NULL";
+                    break;
+
+                case "string":
+                    $item = "\"$item\"";
+                    break;
+            }
+
+
+            return $item;
+        };
+
+        return is_array($values) ? array_map($type_function, $values) : $type_function($values);
+    }
+
+    public static function group_values_and_fields($fields, $values) {
+        $retval = [];
+
+        foreach($fields as $key => $item) {
+            $retval[] = "{$item} = {$values[$key]}";
+        }
+
+        return implode(', ', $retval);
+    }
+
+    public static function prepare_array($sql) {
+        return implode(" ", array_filter($sql)).";";
+    }
+
+    public static function prepare_where($where, $recursion = false, $escaped = false) {
+        $retval = [];
+
+        if (is_array($where)) {
+            $count = count($where);
+            for($i = 0; $i < $count; $i++) {
+                $item = $where[$i];
+
+                if ( ! Arrayobj::array_is_associative($item) ) {
+                    $retval[] = "(".static::prepare_where($item, true, $escaped).")";
+                }
+                else {
+                    $comparison = (isset($item['comparison']) ? $item['comparison'] : "=");
+
+                    # are we having an IN comparison here ...
+                    if ( $is_array = (is_array($item['value']) && count($item['value']) > 1) ) {
+                        switch ($item['comparison']) {
+                            case '=':
+                                $comparison = '=';
+                                break;
+
+                            case '!=':
+                                $comparison = 'not_in';
+                                break;
+                        }
+                    }
+
+                    $value = static::group_fields($item['value'], true);
+
+
+                    switch($comparison) {
+                        case 'match':
+                            $retval[] = static::$syntax[$comparison].' ('.static::fieldname($item['field'], $item['alias'] ?? null).") ".static::$syntax['against'].
+                                " (".(!$escaped || $is_array ? $value : static::escape_values($value))." IN BOOLEAN MODE)".
+                                ($i + 1 < $count ? " ".static::$syntax[ isset($item['operator']) ? $item['operator'] : "and" ] : "");
+
+                            break;
+
+                        default:
+                            $retval[] = static::fieldname($item['field'], $item['alias'] ?? null)." " . ( isset(static::$syntax[$comparison]) ? static::$syntax[$comparison] : $comparison) .
+                                " ".(!$escaped || $is_array ? $value : static::escape_values($value)).
+                                ($i + 1 < $count ? " ".static::$syntax[ isset($item['operator']) ? $item['operator'] : "and" ] : "");
+                            break;
+
+                    }
+                }
+            }
+        }
+
+        return $retval ? ($recursion ? "" : static::$syntax['where'] . " ") . implode(" ", $retval ) : "";
+    }
+
+    public static function prepare_join($joins) {
+        $retval = [];
+
+        if ( is_array($joins) ) {
+            $count = count($joins);
+
+            for($i = 0; $i < $count; $i++) {
+                $join = [];
+
+                $table = Arrayobj::make([
+                    'table_name' => $joins[$i]['table'],
+                    'alias'      => $joins[$i]['alias_right']
+                ]);
+
+                $join[] = static::$syntax[ "join-".$joins[$i]['type'] ] ?? $joins[$i]['type'];
+                $join[] = static::$syntax[ 'join' ];
+                $join[] = static::full_tablename($table);
+                $join[] = static::$syntax[ 'on' ];
+
+                foreach($joins[$i]['fields'] as $left_field => $right_field) {
+                    #$join[] = $joins[$i]['alias_left'].".".static::escape($left_field);
+                    $join[] = static::fieldname($left_field, $joins[$i]['alias_left']);
+                    $join[] = $joins[$i]['comparison'];
+                    $join[] = static::fieldname($right_field, $joins[$i]['alias_right']);
+                }
+
+                $retval[] = implode(' ', $join);
+            }
+
+        }
+
+        return implode(' ', $retval);
+    }
+
+    public static function prepare_order($order, $alias = null)
+    {
+        $retval = [];
+
+        if (is_array($order)) {
+            foreach($order as $item) {
+                $retval[] = static::fieldname($item['field'], $alias).( !empty($item['order']) ? " ".$item['order'] : "" );
+            }
+        }
+
+        return $retval ?  static::$syntax['order_by']." ".implode(', ', $retval) : "";
+    }
+
+    public static function prepare_group($group)
+    {
+        return $group ? static::$syntax['group_by']." ".( is_array($group) ? implode(', ', $group) : $group ) : "";
+    }
+
+    public static function prepare_limit($param)
+    {
+        return implode(' ', array_filter([
+            $param->if_has('limit' , static::$syntax['limit'] ." {$param['limit']}"),
+            $param->if_has('offset', static::$syntax['offset']." {$param['offset']}")
+        ]));
+    }
+
+    public static function fieldname($field, $alias = null)
+    {
+        return strpos($field, '.') ? $field : (!empty($alias) ? $alias."." : "").static::escape($field);
+    }
+
+    public static function escape($field)
+    {
+        return static::$escape_char . str_replace(static::$escape_char, '', $field) . static::$escape_char;
+    }
+}
diff --git a/src/Modeler/Schema.php b/src/Modeler/Schema.php
new file mode 100644
index 0000000..5744811
--- /dev/null
+++ b/src/Modeler/Schema.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Ulmus\Modeler;
+
+class Schema {
+
+    public function __construct()
+    {
+        
+    }
+
+    public function compare()
+    {
+
+    }
+
+    public function migrate()
+    {
+
+    }
+}
diff --git a/src/Query/Explain.php b/src/Query/Explain.php
new file mode 100644
index 0000000..6bd7df1
--- /dev/null
+++ b/src/Query/Explain.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Ulmus\Query;
+
+class Explain extends Fragment {
+
+    public int $order = -1000;
+
+    public bool $extended = false;
+
+    public function render() : string
+    {
+        return $this->renderSegments([
+            "EXPLAIN", $this->extended ? "EXTENDED" : ""
+        ]);
+    }
+}
diff --git a/src/Query/Fragment.php b/src/Query/Fragment.php
new file mode 100644
index 0000000..d17c3de
--- /dev/null
+++ b/src/Query/Fragment.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Ulmus\Query;
+
+abstract class Fragment {
+
+    public int $order = 0;
+
+    public abstract function render() : string;
+
+    protected function renderSegments(array $segments, string $glue = " ") : string
+    {
+        return implode($glue, array_filter($segments));
+    }
+}
diff --git a/src/Query/From.php b/src/Query/From.php
new file mode 100644
index 0000000..5027e9c
--- /dev/null
+++ b/src/Query/From.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace Ulmus\Query;
+
+class From extends Fragment {
+
+    public int $order = -80;
+
+    protected $tables = [];
+
+    public function set(array $tables) : self
+    {
+        $this->tables = $tables;
+        return $this;
+    }
+
+    public function add($table) : self
+    {
+        foreach((array) $table as $alias => $name) {
+            $this->tables[$alias] = $name;
+        }
+
+        return $this;
+    }
+
+    public function render() : string
+    {
+        return $this->renderSegments([
+            'FROM', $this->renderTables(),
+        ]);
+    }
+
+    protected function renderTables() : string
+    {
+        $list = [];
+
+        foreach((array) $this->tables as $alias => $table) {
+            $list[] = ! is_numeric($alias) ? "`$table` $alias" : "`$table`";
+        }
+
+        return implode(", ", $list);
+    }
+}
diff --git a/src/Query/GroupBy.php b/src/Query/GroupBy.php
new file mode 100644
index 0000000..0cb77e0
--- /dev/null
+++ b/src/Query/GroupBy.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Ulmus\Query;
+
+class GroupBy extends Fragment {
+    public int $order = 70;
+
+    public array $groupBy = [];
+
+    public function set(array $order) : self
+    {
+        $this->groupBy = $order;
+        return $this;
+    }
+
+    public function add(string $field, ? string $direction = null) : self
+    {
+        $this->groupBy[] = $field;
+        return $this;
+    }
+
+    public function render() : string
+    {
+        return $this->renderSegments([
+            'GROUP BY', implode(", ", $this->groupBy)
+        ]);
+    }
+
+}
diff --git a/src/Query/Having.php b/src/Query/Having.php
new file mode 100644
index 0000000..8f5ed94
--- /dev/null
+++ b/src/Query/Having.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace Ulmus\Query;
+
+class Having extends Where {
+
+}
diff --git a/src/Query/Insert.php b/src/Query/Insert.php
new file mode 100644
index 0000000..042229c
--- /dev/null
+++ b/src/Query/Insert.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Ulmus\Query;
+
+class Insert extends Fragment {
+
+    public bool $ignore = false;
+
+}
diff --git a/src/Query/Join.php b/src/Query/Join.php
new file mode 100644
index 0000000..c72de34
--- /dev/null
+++ b/src/Query/Join.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace Ulmus\Query;
+
+class Join extends Fragment {
+
+    const TYPE_LEFT = "LEFT";
+    const TYPE_RIGHT = "RIGHT";
+    const TYPE_INNER = "INNER";
+    const TYPE_FULL = "FULL";
+    const TYPE_CROSS = "CROSS";
+    const TYPE_NATURAL = "NATURAL";
+
+    public string $type = self::TYPE_INNER;
+
+    public bool $outer = false;
+
+    public function render() : string
+    {
+        return $this->renderSegments([
+            $this->side,
+            'JOIN',
+            /* table here! */,
+            'ON',
+            /* WHERE ! */
+        ]);
+    }
+}
diff --git a/src/Query/Like.php b/src/Query/Like.php
new file mode 100644
index 0000000..917d780
--- /dev/null
+++ b/src/Query/Like.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace Ulmus\Query;
+
+class Like extends Fragment {
+
+}
diff --git a/src/Query/Limit.php b/src/Query/Limit.php
new file mode 100644
index 0000000..4f8a011
--- /dev/null
+++ b/src/Query/Limit.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Ulmus\Query;
+
+class Limit extends Fragment {
+
+    public int $order = 80;
+
+    protected int $limit = 0;
+
+    public function set($limit) : self
+    {
+        $this->limit = $limit;
+        return $this;
+    }
+
+    public function render() : string
+    {
+        return $this->renderSegments([
+            'LIMIT', $this->limit,
+        ]);
+    }
+}
diff --git a/src/Query/MySQL/Replace.php b/src/Query/MySQL/Replace.php
new file mode 100644
index 0000000..ee9c480
--- /dev/null
+++ b/src/Query/MySQL/Replace.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Ulmus\Query\MySQL;
+
+class Replace extends \Ulmus\Query\Fragment {
+
+    public int $order = -1000;
+
+    public bool $extended = false;
+
+    public function render() : string
+    {
+        return $this->renderSegments([
+            "EXPLAIN", $this->extended ? "EXTENDED" : ""
+        ]);
+    }
+}
diff --git a/src/Query/Offset.php b/src/Query/Offset.php
new file mode 100644
index 0000000..5b84d2b
--- /dev/null
+++ b/src/Query/Offset.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Ulmus\Query;
+
+class Offset extends Fragment {
+
+    public int $order = 81;
+
+    protected int $offset = 0;
+
+    public function set($offset) : self
+    {
+        $this->offset = $offset;
+        return $this;
+    }
+
+    public function render() : string
+    {
+        return $this->renderSegments([
+            'OFFSET', $this->offset,
+        ]);
+    }
+}
diff --git a/src/Query/OrderBy.php b/src/Query/OrderBy.php
new file mode 100644
index 0000000..b88a1d4
--- /dev/null
+++ b/src/Query/OrderBy.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Ulmus\Query;
+
+class OrderBy extends Fragment {
+    public int $order = 70;
+
+    public array $orderBy = [];
+
+    public function set(array $order) : self
+    {
+        $this->orderBy = $order;
+        return $this;
+    }
+
+    public function add(string $field, ? string $direction = null) : self
+    {
+        $this->orderBy[] = [ $field, $direction ];
+        return $this;
+    }
+
+    public function render() : string
+    {
+        $list = array_map(function($item) {
+            list($field, $direction) = $item;
+            return $field . ( $direction ? " $direction" : "" );
+        }, $this->orderBy);
+
+        return $this->renderSegments([
+            'ORDER BY', implode(", ", $list)
+        ]);
+    }
+
+}
diff --git a/src/Query/Select.php b/src/Query/Select.php
new file mode 100644
index 0000000..ecd4bd7
--- /dev/null
+++ b/src/Query/Select.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace Ulmus\Query;
+
+class Select extends Fragment {
+
+    public int $order = -100;
+
+    public bool $distinct = false;
+
+    public bool $union = false;
+
+    public bool $top = false;
+
+    protected $fields = [];
+
+    public function set($fields) : self
+    {
+        $this->fields = is_array($fields) ? $fields : [ $fields ];
+        return $this;
+    }
+
+    public function add($fields) : self
+    {
+        if ( is_array($fields) ) {
+            $this->fields = array_merge($this->fields, $fields);
+        }
+        else {
+            $this->fields[] = $fields;
+        }
+
+        return $this;
+    }
+
+    public function render() : string
+    {
+        return $this->renderSegments([
+            ( $this->union ? 'UNION' : false ),
+            'SELECT',
+            ( $this->top ? 'TOP' : false ),
+            implode(', ', $this->fields)
+        ]);
+    }
+}
diff --git a/src/Query/Where.php b/src/Query/Where.php
new file mode 100644
index 0000000..6c7e5b9
--- /dev/null
+++ b/src/Query/Where.php
@@ -0,0 +1,151 @@
+<?php
+
+namespace Ulmus\Query;
+
+use Ulmus\QueryBuilder;
+
+use Ulmus\Common\EntityField,
+    Ulmus\Common\Sql;
+
+class Where extends Fragment {
+    const OPERATOR_LIKE = "LIKE";
+    const OPERATOR_EQUAL = "=";
+    const OPERATOR_NOT_EQUAL = "<>";
+    const CONDITION_AND = "AND";
+    const CONDITION_OR  = "OR";
+    const CONDITION_NOT = "NOT";
+    const COMPARISON_IN = "IN";
+    const COMPARISON_IS = "IS";
+    const COMPARISON_NULL = "NULL";
+
+    public int $order = 50;
+
+    public array $conditionList;
+
+    public QueryBuilder $queryBuilder;
+
+    public ? Where $parent = null;
+
+    public string $condition = self::CONDITION_AND;
+
+    public function __construct(? QueryBuilder $queryBuilder, $condition = self::CONDITION_AND)
+    {
+        $this->queryBuilder = $queryBuilder;
+        $this->condition = $condition;
+        $this->parent = $queryBuilder->where ?? null;
+    }
+
+    public function add($field, $value, string $operator, string $condition, bool $not = false) : self
+    {
+        $this->conditionList[] = [
+            $field,
+            $value,
+            $operator ?: $this->queryBuilder->conditionOperator,
+            $condition,
+            $not
+        ];
+
+        return $this;
+    }
+
+    public function render() : string
+    {
+        $stack = [];
+
+        foreach ($this->conditionList ?? [] as $key => $item) {
+            if ( $item instanceof Where ) {
+                if ( $item->conditionList ?? false ) {
+                    $stack[] = ( $key !== 0 ? "{$item->condition} " : "" ) . "(" . $item->render() . ")";
+                }
+            }
+            else {
+                list($field, $value, $operator, $condition, $not) = $item;
+                $stack[] = $latest = $this->whereCondition($field, $value, $operator, $key !== 0 ? $condition : "", $not);
+            }
+        }
+
+        return $this->renderSegments([
+            ! $this->parent ? "WHERE" : "",
+            implode(" ", $stack)
+        ]);
+    }
+
+    protected function whereCondition($field, $value, string $operator = self::OPERATOR_EQUAL, string $condition = self::CONDITION_AND, bool $not = false) {
+        return new class($this->queryBuilder, $field, $value, $operator, $condition, $not) {
+
+            public $value;
+            public bool $not = false;
+            public string $field;
+            public string $operator;
+            public string $condition;
+            public QueryBuilder $queryBuilder;
+
+            protected string $content = "";
+
+            public function __construct(QueryBuilder $queryBuilder, string $field, $value, string $operator, string $condition, bool $not) {
+                $this->queryBuilder = $queryBuilder;
+                $this->field = $field;
+                $this->value = $value;
+                $this->condition = $condition;
+                $this->operator = $operator;
+                $this->not = $not;
+            }
+
+            public function render() : string
+            {
+                $value = $this->value();
+
+                return $this->content ?: $this->content = implode(" ", array_filter([
+                    $this->condition,
+                    $this->not ? Where::CONDITION_NOT : "",
+                    $this->field,
+                    $this->operator(),
+                    $value,
+                ]));
+            }
+
+            protected function operator() : string
+            {
+                if ( is_array($this->value) ) {
+                    return (in_array($this->operator, [ '!=', '<>' ]) ? Where::CONDITION_NOT . " " : "") . Where::COMPARISON_IN;
+                }
+
+                return  $this->operator;
+            }
+
+            protected function value()
+            {
+                if ( is_array($this->value) ) {
+                    $stack = [];
+
+                    foreach($this->value as $item) {
+                        $stack[] = $this->filterValue($item);
+                    }
+
+                    return "(" . implode(", ", $stack) . ")";
+                }
+
+                return $this->filterValue($this->value);
+            }
+
+            protected function filterValue($value)
+            {
+                if ( $value === null ) {
+                    $this->operator = in_array($this->operator, [ '!=', '<>' ]) ? Where::COMPARISON_IS . " " . Where::CONDITION_NOT : Where::COMPARISON_IS;
+                    return Where::COMPARISON_NULL;
+                }
+                elseif ( is_object($value) && ( $value instanceof EntityField ) ) {
+                    return $value->name();
+                }
+                else {
+                    return $this->queryBuilder->addParameter($this->value);
+                }
+            }
+
+            public function __toString() : string
+            {
+                return $this->render();
+            }
+        };
+    }
+}
diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php
new file mode 100644
index 0000000..ac0f37a
--- /dev/null
+++ b/src/QueryBuilder.php
@@ -0,0 +1,182 @@
+<?php
+
+namespace Ulmus;
+
+
+class QueryBuilder
+{
+
+    public Query\Where $where;
+
+    /**
+     * Those are the parameters we are going to bind to PDO.
+     */
+    public array $parameters = [];
+
+    public string $conditionOperator = Query\Where::CONDITION_AND;
+
+    public string $entityClass;
+
+    protected int $parameterIndex = 0;
+
+    protected array $queryStack = [];
+
+    public function __construct($entityClass = "") {
+        $this->entityClass = $entityClass;
+    }
+
+    public function select($field) : self
+    {
+        if ( $select = $this->has(Query\Select::class) ) {
+            $select->add($field);
+        }
+        else {
+            $select = new Query\Select();
+            $select->set($field);
+            $this->push($select);
+        }
+
+        return $this;
+    }
+
+    public function from($table, $alias = null, $database = null) : self
+    {
+        if ( $database ) {
+            $table = "`$database`.".$table;
+        }
+
+        if ( $from = $this->has(Query\From::class) ) {
+            $from->add($alias ? [ $alias => $table ] : $table);
+        }
+        else {
+            $from = new Query\From();
+            $this->push($from);
+            $from->set($alias ? [ $alias => $table ] : $table);
+        }
+
+        return $this;
+    }
+
+    public function open(string $condition = Query\Where::CONDITION_AND) : self
+    {
+        if ( false !== ($this->where ?? false) ) {
+            $this->where->conditionList[] = $new = new Query\Where($this, $condition);
+            $this->where = $new;
+        }
+
+        return $this;
+    }
+
+    public function close() : self
+    {
+        if ( false !== ($this->where ?? false) && $this->where->parent ) {
+            $this->where = $this->where->parent;
+        }
+
+        return $this;
+    }
+
+    public function where($field, $value, string $operator = Query\Where::OPERATOR_EQUAL, string $condition = Query\Where::CONDITION_AND, bool $not = false) : self
+    {
+        if ( $this->where ?? false ) {
+            $where = $this->where;
+        }
+        elseif ( false === $where = $this->has(Query\Where::class) ) {
+            $this->where = $where = new Query\Where($this);
+            $this->push($where);
+        }
+
+        $this->conditionOperator = $operator;
+        $where->add($field, $value, $operator, $condition, $not);
+        return $this;
+    }
+
+    public function notWhere($field, $value, string $operator = Query\Where::CONDITION_AND) : self
+    {
+        return $this->where($field, $value, $operator, true);
+    }
+
+    public function groupBy() : self
+    {
+        //$this->queryBuilder->groupBy();
+        return $this;
+    }
+
+    public function limit(int $value) : self
+    {
+        if ( false === $limit = $this->has(Query\Limit::class) ) {
+            $limit = new Query\Limit();
+            $this->push($limit);
+        }
+
+        $limit->set($value);
+        return $this;
+    }
+
+    public function offset(int $value) : self
+    {
+        if ( false === $offset = $this->has(Query\Offset::class) ) {
+            $offset = new Query\Offset();
+            $this->push($offset);
+        }
+
+        $offset->set($value);
+        return $this;
+    }
+
+    public function orderBy(string $field, ? string $direction = null) : self
+    {
+        if ( false === $orderBy = $this->has(Query\OrderBy::class) ) {
+            $orderBy = new Query\OrderBy();
+            $this->push($orderBy);
+        }
+
+        $orderBy->add($field, $direction);
+        return $this;
+    }
+
+    public function push(Query\Fragment $queryFragment) : self
+    {
+        $this->queryStack[] = $queryFragment;
+        return $this;
+    }
+
+    public function render() : string
+    {
+        $sql = [];
+
+        usort($this->queryStack, function($q1, $q2) {
+            return $q1->order <=> $q2->order;
+        });
+
+        foreach($this->queryStack as $fragment) {
+            $sql[] = $fragment->render();
+        }
+
+        return implode(" ", $sql);
+    }
+
+    public function has($class) {
+        foreach($this->queryStack as $item) {
+            if ( get_class($item) === $class ) {
+                return $item;
+            }
+        }
+
+        return false;
+    }
+
+    public function __toString() : string
+    {
+        return $this->render();
+    }
+
+    public function addParameter($value, $key = null) {
+        if ( $key === null ) {
+            $key = ":p" . $this->parameterIndex++;
+        }
+
+        $this->parameters[$key] = $value;
+        return $key;
+    }
+}
diff --git a/src/Repository.php b/src/Repository.php
new file mode 100644
index 0000000..3802ab4
--- /dev/null
+++ b/src/Repository.php
@@ -0,0 +1,254 @@
+<?php
+
+namespace Ulmus;
+
+use Ulmus\Common\EntityResolver;
+
+class Repository
+{
+    const DEFAULT_ALIAS = "this";
+
+    protected ? ConnectionAdapter $adapter;
+
+    protected QueryBuilder $queryBuilder;
+
+    protected EntityResolver $entityResolver;
+
+    public string $alias;
+
+    public string $entityClass;
+
+    public function __construct(string $entity, string $alias = self::DEFAULT_ALIAS, ConnectionAdapter $adapter = null) {
+        $this->entityClass = $entity;
+        $this->alias = $alias;
+        $this->adapter = $adapter;
+        $this->queryBuilder = new QueryBuilder();
+        $this->entityResolver = Ulmus::resolveEntity($entity);
+    }
+
+    public function loadOne() : EntityCollection
+    {
+        return $this->limit(1)->collectionFromQuery();
+    }
+
+    public function loadAll() : EntityCollection
+    {
+        return $this->collectionFromQuery();
+    }
+
+    public function loadFromPk($value) : EntityCollection
+    {
+        return $this->where('id', $value)->loadOne();
+    }
+
+    public function loadFromField($field, $value) : EntityCollection
+    {
+        return $this->where($field, $value)->collectionFromQuery();
+    }
+
+    public function yieldAll() : \Generator
+    {
+
+    }
+
+    public function select($fields) : self
+    {
+        $this->queryBuilder->select($fields);
+        return $this;
+    }
+
+    public function from($table) : self
+    {
+        foreach((array) $table as $alias => $table) {
+            $this->queryBuilder->from($table, is_numeric($alias) ? null : $alias);
+        }
+
+        return $this;
+    }
+
+    public function join(string $type, $table, $field, $value) : self
+    {
+        return $this;
+    }
+
+    public function open(string $condition = Query\Where::CONDITION_AND) : self
+    {
+        $this->queryBuilder->open($condition);
+        return $this;
+    }
+
+    public function orOpen() : self
+    {
+        return $this->open(Query\Where::CONDITION_OR);
+    }
+
+    public function close() : self
+    {
+        $this->queryBuilder->close();
+        return $this;
+    }
+
+    public function where($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self
+    {
+        $this->queryBuilder->where($field, $value, $operator, Query\Where::CONDITION_AND);
+        return $this;
+    }
+
+    public function and($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self
+    {
+        return $this->where($field, $value, $operator);
+    }
+
+    public function or($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self
+    {
+        $this->queryBuilder->where($field, $value, $operator, Query\Where::CONDITION_OR);
+        return $this;
+    }
+
+    public function notWhere(array $condition) : self
+    {
+        $this->queryBuilder->where($field, $value, $operator, Query\Where::CONDITION_AND, true);
+        return $this;
+    }
+
+    public function orNot($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self
+    {
+        $this->queryBuilder->notWhere($condition, Query\Where::CONDITION_OR, true);
+        return $this;
+    }
+
+    public function having() : self
+    {
+        return $this;
+    }
+
+    public function notHaving() : self
+    {
+        return $this;
+    }
+
+    public function in($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self
+    {
+        $this->queryBuilder->where($field, $value, $operator);
+        return $this;
+    }
+
+    public function orIn($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self
+    {
+        $this->queryBuilder->where($field, $value, $operator, Query\Where::CONDITION_OR);
+        return $this;
+    }
+
+    public function notIn($field, $value) : self
+    {
+        $this->queryBuilder->where($field, $value, Query\Where::OPERATOR_NOT_EQUAL);
+        return $this;
+    }
+
+    public function orNotIn($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self
+    {
+        return $this->orNot($field, $value, Query\Where::OPERATOR_NOT_EQUAL, Query\Where::CONDITION_OR);
+    }
+
+    public function like($field, $value) : self
+    {
+        $this->queryBuilder->where($field, $value, Query\Where::OPERATOR_LIKE, Query\Where::CONDITION_AND);
+        return $this;
+    }
+
+    public function notLike($field, $value) : self
+    {
+        $this->queryBuilder->where($field, $value, Query\Where::OPERATOR_LIKE, Query\Where::CONDITION_AND, true);
+        return $this;
+    }
+
+    public function match() : self
+    {
+
+    }
+
+    public function notMatch() : self
+    {
+
+    }
+
+    public function between() : self
+    {
+
+    }
+
+    public function notBetween() : self
+    {
+
+    }
+
+    public function groupBy() : self
+    {
+        #$this->queryBuilder->groupBy();
+        return $this;
+    }
+
+    public function orderBy(string $field, ? string $direction = null) : self
+    {
+        $this->queryBuilder->orderBy($field, $direction);
+        return $this;
+    }
+
+    public function limit(int $value) : self
+    {
+        $this->queryBuilder->limit($value);
+        return $this;
+    }
+
+    public function offset(int $value) : self
+    {
+        $this->queryBuilder->offset($value);
+        return $this;
+    }
+
+    public function commit() : self
+    {
+        return $this;
+    }
+
+    public function rollback() : self
+    {
+        return $this;
+    }
+
+    protected function collectionFromQuery() : EntityCollection
+    {
+        $class = $this->entityClass;
+
+        $entityCollection = new EntityCollection();
+
+        foreach(Ulmus::iterateQueryBuilder($this->selectSqlQuery()->queryBuilder) as $entityData) {
+            $entityCollection->append( ( new $class() )->entityFillFromDataset($entityData) );
+        }
+
+        return $entityCollection;
+    }
+
+    protected function selectSqlQuery() : self
+    {
+        if ( ! $this->queryBuilder->has(Query\Select::class) ) {
+            $this->select("{$this->alias}.*");
+        }
+
+        if ( ! $this->queryBuilder->has(Query\From::class) ) {
+            $this->from([ $this->alias => $this->entityResolver->tableName() ]);
+        }
+
+        return $this;
+    }
+
+    protected function fromRow($row) : self
+    {
+
+    }
+
+    protected function fromCollection($rows) : self
+    {
+
+    }
+}
diff --git a/src/Ulmus.php b/src/Ulmus.php
new file mode 100644
index 0000000..f7f322e
--- /dev/null
+++ b/src/Ulmus.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace Ulmus;
+
+use Generator;
+
+abstract class Ulmus
+{
+    public static string $repositoryClass = "\\Ulmus\\Repository";
+
+    public static string $queryBuilderClass = "\\Ulmus\\QueryBuilder";
+
+    public static ConnectionAdapter $defaultAdapter;
+
+    public static array $resolved = [];
+
+    protected static function fetchQueryBuilder(QueryBuilder $queryBuilder, ?ConnectionAdapter $adapter = null) : array
+    {
+        $sql = $queryBuilder->render();
+        return ( $adapter ?: static::$defaultAdapter )->pdo->select($sql, $queryBuilder->parameters ?? [])->fetchAll();
+    }
+
+    public static function iterateQueryBuilder(QueryBuilder $queryBuilder, ?ConnectionAdapter $adapter = null) : Generator
+    {
+        $sql = $queryBuilder->render();
+        $statement = ( $adapter ?: static::$defaultAdapter )->pdo->select($sql, $queryBuilder->parameters ?? []);
+
+        while ( $row = $statement->fetch() ) {
+            yield $row;
+        }
+
+        $statement->closeCursor();
+
+        return [
+            'count' => $statement->rowCount(),
+        ];
+    }
+
+    public static function resolveEntity(string $entityClass) : Common\EntityResolver
+    {
+        return static::$resolved[$entityClass] ?? static::$resolved[$entityClass] = new Common\EntityResolver($entityClass);
+    }
+
+    public static function repository(...$arguments) : Repository
+    {
+        return new static::$repositoryClass(...$arguments);
+    }
+
+    public static function queryBuilder(...$arguments) : QueryBuilder
+    {
+        return new static::$queryBuilderClass(...$arguments);
+    }
+}