From f9c002f08a63be537adc8eeab8508355fe941aef Mon Sep 17 00:00:00 2001
From: Juan Ferrer Toribio <juan.ferrer.toribio@gmail.com>
Date: Mon, 13 Nov 2017 17:36:30 +0100
Subject: [PATCH] backup

---
 forms/ecomerce/catalog/catalog.js | 156 +++++++++++++++++++++++++++---
 forms/ecomerce/catalog/ui.xml     | 132 +++++++------------------
 js/db/model.js                    |  40 +++++---
 js/db/query.js                    |   2 +-
 js/sql/delete.js                  |  14 +--
 js/sql/field.js                   |   9 +-
 js/sql/filter-item.js             |  14 ++-
 js/sql/filter.js                  |  24 +++--
 js/sql/function.js                |  41 ++++++--
 js/sql/insert.js                  |  33 ++-----
 js/sql/join-item.js               |  49 ++++++++++
 js/sql/join.js                    |  59 +++++++++++
 js/sql/list-holder.js             |  58 +++++++++++
 js/sql/multi-stmt.js              |  53 +++++-----
 js/sql/object.js                  |  80 +++++++++++++--
 js/sql/operation.js               |  65 +++++--------
 js/sql/select.js                  |  17 ++--
 js/sql/sql.js                     |   3 +
 js/sql/stmt.js                    |  36 +++----
 js/sql/table.js                   |  19 +++-
 js/sql/update.js                  |  27 +++---
 js/vn/lot-query.js                |  18 +++-
 js/vn/lot.js                      |  14 ++-
 23 files changed, 638 insertions(+), 325 deletions(-)
 create mode 100644 js/sql/join-item.js
 create mode 100644 js/sql/join.js
 create mode 100644 js/sql/list-holder.js

diff --git a/forms/ecomerce/catalog/catalog.js b/forms/ecomerce/catalog/catalog.js
index e1472745..443ea984 100644
--- a/forms/ecomerce/catalog/catalog.js
+++ b/forms/ecomerce/catalog/catalog.js
@@ -104,7 +104,7 @@ Hedera.Catalog = new Class
 			Db.Model.SortWay.ASC : Db.Model.SortWay.DESC;
 		
 		if (sortField)
-			this.$.items.sortByName (sortField, sortWay);
+			this.$.items.sort (sortField, sortWay);
 		
 		this.hideMenu ();
 	}
@@ -113,13 +113,7 @@ Hedera.Catalog = new Class
 	{
 		var params = this.params.$;
 		var shouldRefresh = params.search ||
-			params.realm && (
-				params.type ||
-				params.color ||
-				params.origin ||
-				params.category ||
-				params.producer
-			);
+			params.realm && params.type;
 
 		if (shouldRefresh)
 		{
@@ -130,20 +124,154 @@ Hedera.Catalog = new Class
 			this.$.items.refresh (modelParams);
 
 			this.hideMenu ();
-			this.$.order.style.display = 'block';
 		}
 		else
-		{
 			this.$.items.clean ();
-			this.$.order.style.display = 'none';
+
+		this.showFilters (params.search || params.realm);
+	}
+	
+	,showFilters: function (show)
+	{
+		this.$.filters.style.display = show ? 'block' : 'none';
+		this.$.realmMsg.style.display = show ? 'none' : 'block';
+		this.$.order.style.display = show ? 'block' : 'none';
+	}
+
+	,onRemoveFiltersClick: function ()
+	{
+		this.params.$ = {};
+	}
+
+	,tagFilter: {}
+
+	,onTagFilterAdd: function ()
+	{
+		tagFilter[tag.id] = tag.value;
+	}
+
+	,buildTagFilter: function (excludeTag)
+	{
+		var Type = Sql.Operation.Type;
+
+		var join = new Sql.Join ({
+			target: Sql.Table ({
+				schema: 'vn',
+				name: 'item',
+				alias: 'i'
+			})
+		});
+
+		join.push (new Sql.JoinItem ({
+			target: new Sql.Table ({
+				schema: 'vn',
+				name: 'family',
+				alias: 'f'
+			}),
+			condition: new Sql.Operation ({
+				type: Type.EQUAL,
+				exprs: [
+					new Sql.Field ({
+						target: 'f',
+						name: 'id'
+					}),
+					new Sql.Field ({
+						target: 'i',
+						name: 'familyFk'
+					})
+				]
+			})
+		}));
+
+		var i = -1;
+
+		for (var tagId in this.tagFilter)
+		{
+			var tagValue = this.tagFilter[tagId];
+
+			if (tagId == excludeTag)
+				continue;
+	
+			i++;
+			var tAlias = 'it'+ i;
+
+			var joinItem = new Sql.JoinItem ({
+				target: new Sql.Table ({
+					schema: 'vn',
+					name: 'itemTag',
+					alias: tAlias
+				}),
+				condition: new Sql.Operation ({
+					type: Type.AND
+				})
+			});
+			join.push (joinItem);
+	
+			joinItem.condition.exprs = [
+				new Sql.Operation ({
+					type: Type.EQUAL,
+					exprs: [
+						new Sql.Field ({
+							target: tAlias,
+							name: 'itemFk'
+						}),
+						new Sql.Field ({
+							target: 'i',
+							name: 'id'
+						})
+					]
+				}),
+				new Sql.Operation ({
+					type: Type.EQUAL,
+					exprs: [
+						new Sql.Field ({
+							target: tAlias,
+							name: 'tagFk'
+						}),
+						new Sql.Value ({
+							value: tagId
+						})
+					]
+				}),
+				new Sql.Operation ({
+					type: Type.EQUAL,
+					exprs: [
+						new Sql.Field ({
+							target: tAlias,
+							name: 'value'
+						}),
+						new Sql.Value ({
+							value: tagValue
+						})
+					]
+				})
+			];
 		}
 	}
 	
+	,onTagsReady: function (resultSet)
+	{
+		var tags = resultSet.fetchArray ();
+
+		tags.forEach (function (tag) {
+			var model = new Db.Model ({
+				autoLoad: false,
+				query: query
+			});
+
+			var combo = new Vn.Combo ({
+				name: tag.name,
+				lot: lot,
+				placeholder: tag.description,
+				model: model
+			});
+
+			div.appendChild (combo);
+		});
+	}
+
 	,onRealmChange: function ()
 	{
-		var newValue = this.params.$.realm;
-		this.$.filters.style.display = newValue ? 'block' : 'none';
-		this.$.realmMsg.style.display = newValue ? 'none' : 'block';
 		this.refreshTitleColor ();
 	}
 
diff --git a/forms/ecomerce/catalog/ui.xml b/forms/ecomerce/catalog/ui.xml
index 4abf7936..1f66a3ed 100755
--- a/forms/ecomerce/catalog/ui.xml
+++ b/forms/ecomerce/catalog/ui.xml
@@ -1,43 +1,39 @@
 <vn>
 <vn-group>
 	<vn-lot-query id="params" on-change="onParamsChange">
+		<vn-spec name="search" type="String"/>
+		<vn-spec name="itemId" type="Number"/>
 		<vn-spec name="realm" type="Number"/>
 		<vn-spec name="type" type="Number"/>
-		<vn-spec name="search" type="String"/>
-		<vn-spec name="color" type="String"/>
-		<vn-spec name="origin" type="Number"/>
-		<vn-spec name="category" type="String"/>
-		<vn-spec name="producer" type="Number"/>
-		<vn-spec name="itemId" type="Number"/>
 	</vn-lot-query>
 	<sql-filter type="AND" id="filter">
 		<sql-filter-item type="EQUAL"
-			target="a" field="tipo_id"
+			target="i" field="familyFk"
 			param="type"/>
 		<sql-filter type="OR">
 			<sql-filter-item type="LIKE"
-				target="a" field="Article"
+				target="i" field="name"
 				param="search"/>
 			<sql-filter-item type="EQUAL"
-				target="a" field="Id_Article"
+				target="i" field="id"
 				param="search"/>
 			<sql-filter-item type="EQUAL"
-				target="a" field="Id_Article"
+				target="i" field="id"
 				param="itemId"/>
 		</sql-filter>
-		<sql-filter-item type="EQUAL"
-			target="a" field="Color"
-			param="color"/>
-		<sql-filter-item type="EQUAL"
-			target="a" field="id_origen"
-			param="origin"/>
-		<sql-filter-item type="EQUAL"
-			target="a" field="Categoria"
-			param="category"/>
-		<sql-filter-item type="EQUAL"
-			target="a" field="producer_id"
-			param="producer"/>
 	</sql-filter>
+	<vn-string id="pre-query">
+		DROP TEMPORARY TABLE IF EXISTS tItems;
+			CREATE TEMPORARY TABLE tItems
+				(INDEX (id))
+				ENGINE = MEMORY
+				SELECT i.id
+					FROM #joins
+					WHERE #filter;
+	</vn-string>
+	<vn-string id="post-query">
+		DROP TEMPORARY TABLE tItems;
+	</vn-string>
 	<db-model
 		id="items"
 		result-index="2"
@@ -45,9 +41,9 @@
 		CREATE TEMPORARY TABLE tmp.bionic_calc
 			(INDEX (item_id))
 			ENGINE = MEMORY
-			SELECT a.Id_Article item_id
-				FROM vn2008.Articles a
-					JOIN vn2008.Tipos t ON t.tipo_id = a.tipo_id
+			SELECT i.id item_id
+				FROM vn.item i
+					JOIN vn.family f ON f.id = i.familyFk
 				WHERE #filter;
 		CALL bionic_calc ();
 		SELECT a.Id_Article item_id, a.description, b.available, b.price,
@@ -75,6 +71,15 @@
 			FROM basket_item
 			GROUP BY warehouse_id
 	</db-query>
+	<db-query id="tags" on-ready="onTagsReady">
+		SELECT it.tagFk, SUM(it.priority) priority
+			FROM vn.itemTag it
+				JOIN vn.item i ON i.id = it.itemFk
+			WHERE #filter
+			GROUP BY tagFk
+			ORDER BY priority DESC
+			LIMIT 6
+	</db-query>
 	<db-form id="card-extend">
 		<db-model
 			property="model"
@@ -215,10 +220,10 @@
 		</div>
 		<div id="filters" class="filters">
 			<h2>_Filter by</h2>
-			<vn-filter
+			<label>_Family</label>
+			<htk-combo
 				name="type"
-				lot="params"
-				placeholder="_Family">
+				lot="params">
 				<db-model
 					id="types"
 					property="model"
@@ -234,73 +239,10 @@
 						WHERE t.reino_id = #realm
 						ORDER BY name
 				</db-model>
-			</vn-filter>
-			<vn-filter
-				id="test"
-				name="color"
-				lot="params"
-				placeholder="_Color"
-				filter="filter">
-				<db-model property="model" auto-load="false" result-index="1">
-					CALL item_available ();
-					SELECT DISTINCT c.Id_Tinta, l.str name
-						FROM vn2008.Tintas c
-							JOIN vn2008.Articles a ON a.Color = c.Id_Tinta
-							JOIN vn2008.Tipos t ON t.tipo_id = a.tipo_id
-							LEFT JOIN vn_locale.color_view l ON l.color_id = c.Id_Tinta
-							JOIN tmp.item_available i ON i.item_id = a.Id_Article
-						WHERE #filter
-						ORDER BY name
-				</db-model>
-			</vn-filter>
-			<vn-filter
-				name="producer"
-				lot="params"
-				placeholder="_Producer"
-				filter="filter">
-				<db-model property="model" auto-load="false" result-index="1">
-					CALL item_available ();
-					SELECT DISTINCT p.producer_id, p.name
-						FROM vn2008.producer p
-							JOIN vn2008.Articles a ON a.producer_id = p.producer_id
-							JOIN vn2008.Tipos t ON t.tipo_id = a.tipo_id
-							JOIN tmp.item_available i ON i.item_id = a.Id_Article
-						WHERE #filter
-						ORDER BY name
-				</db-model>
-			</vn-filter>
-			<vn-filter
-				name="origin"
-				lot="params"
-				placeholder="_Origin"
-				filter="filter">
-				<db-model property="model" auto-load="false" result-index="1">
-					CALL item_available ();
-					SELECT DISTINCT o.id, l.str name, o.Abreviatura
-						FROM vn2008.Origen o
-							JOIN vn2008.Articles a ON a.id_origen = o.id
-							JOIN vn2008.Tipos t ON t.tipo_id = a.tipo_id
-							LEFT JOIN vn_locale.origin_view l ON l.origin_id = o.id
-							JOIN tmp.item_available i ON i.item_id = a.Id_Article
-						WHERE #filter
-						ORDER BY name
-				</db-model>
-			</vn-filter>
-			<vn-filter
-				name="category"
-				lot="params"
-				placeholder="_Category"
-				filter="filter">
-				<db-model property="model" auto-load="false" result-index="1">
-					CALL item_available ();
-					SELECT DISTINCT a.Categoria, a.Categoria category
-						FROM vn2008.Articles a
-							JOIN vn2008.Tipos t ON t.tipo_id = a.tipo_id
-							JOIN tmp.item_available i ON i.item_id = a.Id_Article
-						WHERE #filter
-						ORDER BY a.Categoria
-				</db-model>
-			</vn-filter>
+			</htk-combo>
+			<button on-click="onRemoveFiltersClick">
+				_Remove filters
+			</button>
 		</div>
 		<div id="order" class="order">
 			<h2>_Order by</h2>
diff --git a/js/db/model.js b/js/db/model.js
index 1c43b28a..8dd01e78 100644
--- a/js/db/model.js
+++ b/js/db/model.js
@@ -743,7 +743,7 @@ Klass.implement
 		var stmts = new Sql.MultiStmt ();
 		
 		var query = new Sql.String ({query: 'START TRANSACTION'});
-		stmts.addStmt (query);
+		stmts.push (query);
 
 		for (var i = 0; i < ops.length; i++)
 		{
@@ -759,7 +759,10 @@ Klass.implement
 
 				if (where)
 				{
-					query = new Sql.Delete ({where: where});
+					query = new Sql.Delete ({
+						where: where,
+						limit: 1
+					});
 					query.addTarget (this._createTarget (this._mainTable));
 				}
 			}
@@ -770,13 +773,13 @@ Klass.implement
 				for (var tableIndex in op.tables)
 				{
 					var stmt = this._createDmlQuery (op, parseInt (tableIndex));
-					query.addStmt (stmt);
+					query.push (stmt);
 				}
 			}
 
 			if (query)
 			{
-				stmts.addStmt (query);
+				stmts.push (query);
 			}
 			else
 			{
@@ -786,7 +789,7 @@ Klass.implement
 		}
 
 		var query = new Sql.String ({query: 'COMMIT'});
-		stmts.addStmt (query);
+		stmts.push (query);
 	
 		this._conn.execStmt (stmts,
 			this._onOperationsDone.bind (this, ops));
@@ -804,7 +807,10 @@ Klass.implement
 		var multiStmt = new Sql.MultiStmt ();
 		var target = this._createTarget (tableIndex);
 		
-		var select = new Sql.Select ({where: where});
+		var select = new Sql.Select ({
+			where: where,
+			limit: 1
+		});
 		select.addTarget (target);
 
 		var row = op.row;
@@ -846,7 +852,10 @@ Klass.implement
 			if (!updateWhere)
 				return null;
 		
-			var dmlQuery = new Sql.Update ({where: updateWhere});
+			var dmlQuery = new Sql.Update ({
+				where: updateWhere,
+				limit: 1
+			});
 		
 			for (var i = 0; i < cols.length; i++)
 			if (cols[i].table === tableIndex && op.oldValues[cols[i].name] !== undefined)
@@ -859,8 +868,8 @@ Klass.implement
 
 		dmlQuery.addTarget (target);
 
-		multiStmt.addStmt (dmlQuery);
-		multiStmt.addStmt (select);
+		multiStmt.push (dmlQuery);
+		multiStmt.push (select);
 		return multiStmt;
 	}
 	
@@ -947,7 +956,8 @@ Klass.implement
 
 	,_createWhere: function (tableIndex, op, useOldValues)
 	{
-		var where = new Sql.Operation ({type: Sql.Operation.Type.AND});
+		var Type = Sql.Operation.Type;
+		var where = new Sql.Operation ({type: Type.AND});
 		var pks = this.tables[tableIndex].pks;
 		
 		if (pks.length === 0)
@@ -957,9 +967,9 @@ Klass.implement
 		{
 			var column = this.columns[pks[i]];
 		
-			var equalOp = new Sql.Operation ({type: Sql.Operation.Type.EQUAL});
-			equalOp.exprs.add (new Sql.Field ({name: column.orgname}));
-			where.exprs.add (equalOp);
+			var equalOp = new Sql.Operation ({type: Type.EQUAL});
+			equalOp.push (new Sql.Field ({name: column.orgname}));
+			where.push (equalOp);
 
 			var pkValue = null;
 			
@@ -970,9 +980,9 @@ Klass.implement
 				pkValue = op.row[column.name];
 
 			if (pkValue)
-				equalOp.exprs.add (new Sql.Value ({value: pkValue}));
+				equalOp.push (new Sql.Value ({value: pkValue}));
 			else if (column.flags & Connection.Flag.AI && !useOldValues)
-				equalOp.exprs.add (new Sql.Function ({name: 'LAST_INSERT_ID'}));
+				equalOp.push (new Sql.Function ({name: 'LAST_INSERT_ID'}));
 			else
 				return null;
 		}
diff --git a/js/db/query.js b/js/db/query.js
index b23f30d3..d51c0f6a 100644
--- a/js/db/query.js
+++ b/js/db/query.js
@@ -64,7 +64,7 @@ module.exports = new Class
 			,set: function (x)
 			{
 				this.link ({_lot: x}, {'change': this.onChange});
-				this._autoLoad ();
+				this.onChange ();
 			}
 			,get: function ()
 			{
diff --git a/js/sql/delete.js b/js/sql/delete.js
index f4de11c3..5dfbc1e3 100644
--- a/js/sql/delete.js
+++ b/js/sql/delete.js
@@ -7,17 +7,17 @@ var Stmt = require ('./stmt');
 module.exports = new Class
 ({
 	Extends: Stmt
+	,Tag: 'sql-delete'
 
-	,render: function (batch)
+	,render: function (params)
 	{
-		var sql = 'DELETE FROM ' + this.renderTarget (batch);
+		var sql = 'DELETE FROM '
+			+ this.renderTarget (params);
 		
 		if (this.where)
-			sql += ' WHERE ' + this.where.render (batch);
-			
-		sql += ' LIMIT 1';	// Only for security.
-			
+			sql += ' WHERE ' + this.where.render (params);
+
+		sql += this.renderLimit(params);
 		return sql;
 	}
 });
-
diff --git a/js/sql/field.js b/js/sql/field.js
index e6967331..29904b6d 100644
--- a/js/sql/field.js
+++ b/js/sql/field.js
@@ -32,7 +32,12 @@ module.exports = new Class
 
 	,render: function ()
 	{
-		var sql = (this.target) ? '`' + this.target + '`.' : '';	
-		return sql + '`' + this.name + '`';
+		var sql = '';
+		
+		if (this.target)
+			sql += this.renderIdentifier (this.target) +'.';
+
+		sql += this.renderIdentifier (this.name);	
+		return sql;
 	}
 });
diff --git a/js/sql/filter-item.js b/js/sql/filter-item.js
index c730a980..a25bd228 100644
--- a/js/sql/filter-item.js
+++ b/js/sql/filter-item.js
@@ -50,26 +50,24 @@ module.exports = new Class
 
 	,render: function (params)
 	{
-		var op = new Operation ({type: this.type});
+		var newOp = new Operation ({type: this.type});
 
-		var field = new Field ({
+		newOp.push (new Field ({
 			name: this.field,
 			target: this.target
-		});
-		op.appendChild (field);
+		}));
 
 		var value = params[this.param];
 
-		if (this.type === Operation.Type.LIKE && typeof value == 'string')
+		if (this.type === Operation.Type.LIKE && typeof value === 'string')
 		{
 			value = value.replace (/[\%\?]/g, this._escapeChar);
 			value = value.replace (/^|\s+|$/g, '%');
 		}
 
-		var sqlValue = new Value ({value: value});
-		op.appendChild (sqlValue);
+		newOp.push (new Value ({value: value}));
 			
-		return op.render (params);
+		return newOp.render (params);
 	}
 
 	,_escapeChar: function (char)
diff --git a/js/sql/filter.js b/js/sql/filter.js
index 5b557fcf..8617d42b 100644
--- a/js/sql/filter.js
+++ b/js/sql/filter.js
@@ -16,7 +16,7 @@ module.exports = new Class
 	 */
 	,isReady: function (params)
 	{
-		var exprs = this.exprs.objects;
+		var exprs = this.exprs;
 		for (var i = exprs.length; i--;)
 		if (exprs[i].isReady (params))
 			return true;
@@ -31,16 +31,22 @@ module.exports = new Class
 	 */
 	,render: function (params)
 	{
-		var op = new Operation ({type: this.type});
-		var exprs = this.exprs.objects;
+		var newOp;
+		var newExprs = [];
 
-		for (var i = 0; i < exprs.length; i++)
-		if (exprs[i].isReady (params))
-			op.exprs.add (exprs[i]);
+		this.exprs.forEach (function (expr) {
+			if (expr.isReady (params))
+				newExprs.push (exprs[i]);
+		})
 	
-		if (op.exprs.objects.length == 0)
-			op = new Value ({value: true});
+		if (newExprs.length > 0)
+			newOp = new Operation ({
+				type: this.type,
+				exprs: newExprs
+			});
+		else
+			newOp = new Value ({value: true});
 			
-		return op.render (params);
+		return newOp.render (params);
 	}
 });
diff --git a/js/sql/function.js b/js/sql/function.js
index b24df9d9..c6dbf4e7 100644
--- a/js/sql/function.js
+++ b/js/sql/function.js
@@ -1,38 +1,61 @@
 
 var Expr = require ('./expr');
-var List = require ('./list');
+var ListHolder = require ('./list-holder');
 
 /**
  * The equivalent of a SQL function.
- *
- * @param {string} funcName The name of the function
- * @param {Array#Sql.Expr} param Array with function parameters
  */
 module.exports = new Class
 ({
 	Extends: Expr
+	,Tag: 'sql-function'
+	,Implements: ListHolder
 	,Properties:
 	{
+		/**
+		 * The function name.
+		 */
 		name:
 		{
 			type: String
 			,value: null
 		},
+		/**
+		 * The function schema.
+		 */
 		schema:
 		{
 			type: String
 			,value: null
 		},
+		/**
+		 * The function parameters.
+		 */
 		params:
 		{
-			type: List
-			,value: null
+			type: Array
+			,set: function (x)
+			{
+				this.list = x;
+			}
+			,get: function ()
+			{
+				return this.list;
+			}
 		}
 	}
 
-	,render: function (batch)
+	,render: function (params)
 	{
-		var sql = (this.schema) ? '`' + this.schema + '`.' : '';
-		return sql + '`' + this.name + '`()';
+		var sql = '';
+		
+		if (this.schema)
+			sql += this.renderIdentifier (this.schema) +'.';
+
+		sql += this.renderIdentifier (this.name)
+			+ '('
+			+ this.renderListWs (this.list, params, ', ')
+			+ ')';
+		return sql;
 	}
 });
diff --git a/js/sql/insert.js b/js/sql/insert.js
index a74d29ae..34bff4b9 100644
--- a/js/sql/insert.js
+++ b/js/sql/insert.js
@@ -8,31 +8,14 @@ module.exports = new Class
 ({
 	Extends: Dml
 
-	,render: function (batch)
+	,render: function (params)
 	{
-		var sql;
-		var n;
-		
-		sql = 'INSERT INTO ' + this.renderTarget (batch) + ' (';
-		
-		for (n = 0; n < this.field.length; n++)
-		{
-			if (n > 0)
-				sql += ', ';
-			sql += this.field[n].render (batch);
-		}
-		
-		sql += ') VALUES (';
-
-		for (n = 0; n < this.field.length; n++)
-		{
-			if (n > 0)
-				sql += ', ';
-			sql += this.expr[n].render(batch);
-		}
-		
-		sql += ')';
-			
-		return sql;
+		return 'INSERT INTO '
+			+ this.renderTarget (params)
+			+ ' ('
+			+ this.renderListWs (this.field, params, ', ')
+			+ ') VALUES ('
+			+ this.renderListWs (this.expr, params, ', ')
+			+ ')';
 	}
 })
diff --git a/js/sql/join-item.js b/js/sql/join-item.js
new file mode 100644
index 00000000..f250ec7f
--- /dev/null
+++ b/js/sql/join-item.js
@@ -0,0 +1,49 @@
+
+var Target = require ('./target');
+var Expr = require ('./expr');
+var SqlObject = require ('./object');
+var Type = require ('./join').Type;
+var TypeSql = require ('./join').TypeSql;
+
+/**
+ * The equivalent of a SQL join.
+ */
+module.exports = new Class
+({
+	Extends: SqlObject
+	,Tag: 'sql-join-table'
+	,Properties:
+	{
+		/**
+		 * The join type.
+		 */
+		type:
+		{
+			enumType: Type
+			,value: 0
+		},
+		/**
+		 * The right target.
+		 */
+		target:
+		{
+			type: Target
+			,value: null
+		},
+		/**
+		 * The join on condition.
+		 */
+		condition:
+		{
+			type: Expr
+			,value: null
+		}
+	}
+
+	,render: function (params)
+	{
+		return TypeSql[this.type] +' JOIN '
+			+ this.target.render (params)
+			+ this.renderIfSet (this.condition, 'ON', params);
+	}
+});
diff --git a/js/sql/join.js b/js/sql/join.js
new file mode 100644
index 00000000..45c10896
--- /dev/null
+++ b/js/sql/join.js
@@ -0,0 +1,59 @@
+
+var Target = require ('./target');
+var ListHolder = require ('./list-holder');
+
+/**
+ * The equivalent of a SQL join.
+ */
+var Klass = new Class ();
+module.exports = Klass;
+
+var Type = {
+	INNER : 0,
+	LEFT  : 1,
+	RIGHT : 2
+};
+
+var TypeSql = [
+	'INNER',
+	'LEFT',
+	'RIGHT'
+];
+
+Klass.extend
+({
+	Type: Type,
+	TypeSql: TypeSql
+});
+
+Klass.implement
+({
+	Extends: Target
+	,Implements: ListHolder
+	,Tag: 'sql-join'
+	,Properties:
+	{
+		/**
+		 * The right targets.
+		 */
+		targets:
+		{
+			type: Array
+			,set: function (x)
+			{
+				this.list = x;
+			}
+			,get: function ()
+			{
+				return this.list;
+			}
+		}
+	}
+
+	,render: function (params)
+	{
+		return '('+ this.target.render (params) +' '
+			+ this.renderList (this.list, params)
+			+ ')';
+	}
+});
diff --git a/js/sql/list-holder.js b/js/sql/list-holder.js
new file mode 100644
index 00000000..34a01cdf
--- /dev/null
+++ b/js/sql/list-holder.js
@@ -0,0 +1,58 @@
+/**
+ * Interface for array holders.
+ */
+module.exports = new Class
+({
+	Properties:
+	{
+		list:
+		{
+			type: Array
+			,set: function (x)
+			{
+				this._list = x;
+			}
+			,get: function ()
+			{
+				return this._list;
+			}
+		}
+	}
+
+	,_list: []
+
+	,appendChild: function (child)
+	{
+		this._list.push (child);
+	}
+
+	/**
+	 * Adds an element to the list.
+	 * 
+	 * @param {SqlObject} element The element to add
+	 */
+	,push: function (element)
+	{
+		this._list.push (element);
+	}
+	
+	/**
+	 * Removes an element from the list.
+	 *
+	 * @param {Number} i The element index
+	 */
+	,splice: function (i)
+	{
+		this._list.splice (i);
+	}
+	
+	/**
+	 * Adds an element to the list.
+	 *
+	 * @param {Number} i The element index
+	 */
+	,get: function (i)
+	{
+		return this._list[i];
+	}
+});
diff --git a/js/sql/multi-stmt.js b/js/sql/multi-stmt.js
index ecd91a91..25efc4d3 100644
--- a/js/sql/multi-stmt.js
+++ b/js/sql/multi-stmt.js
@@ -1,5 +1,6 @@
 
 var Stmt = require ('./stmt');
+var ListHolder = require ('./list-holder');
 
 /**
  * The equivalent of a SQL multi statement.
@@ -7,42 +8,34 @@ var Stmt = require ('./stmt');
 module.exports = new Class
 ({
 	Extends: Stmt
-
-	,stmts: []
-
-	,addStmt: function (stmt)
+	,Implements: ListHolder
+	,Tag: 'sql-multi-stmt'
+	,Properties:
 	{
-		return this.stmts.push (stmt);
-	}
-	
-	,getStmt: function (stmtIndex)
-	{
-		return this.stmts[index];
+		/**
+		 * The statements list.
+		 */
+		stmts:
+		{
+			type: Array
+			,set: function (x)
+			{
+				this.list = x;
+			}
+			,get: function ()
+			{
+				return this.list;
+			}
+		}
 	}
 
-	,isReady: function ()
-	{
-		if (this.stmts.length == 0)
-			return false;
-	
-		for (var i = 0; i < this.stmts.length; i++)
-			if (!this.stmts[i].isReady ())
-				return false;
-				
-		return true;
-	}
-
-	,render: function (batch)
+	,render: function (params)
 	{
 		var sql = '';
 
-		for (var i = 0; i < this.stmts.length; i++)
-		{
-			if (i > 0)
-				sql += ";\n";
-
-			sql += this.stmts[i].render (batch);
-		}
+		this._list.forEach (function (stmt) {
+			sql += stmt.render (params) +";\n";
+		});
 
 		return sql;
 	}
diff --git a/js/sql/object.js b/js/sql/object.js
index dae2e8e5..dac1fec0 100644
--- a/js/sql/object.js
+++ b/js/sql/object.js
@@ -4,14 +4,6 @@
 module.exports = new Class
 ({
 	Extends: Vn.Object
-	
-	/**
-	 * Renders the object as an SQL string.
-	 *
-	 * @param {Object} params The params used to render the object
-	 * @return {string} The SQL string
-	 */
-	,render: function () {}
 
 	/**
 	 * Gets if the object is ready to be rendered.
@@ -30,4 +22,76 @@ module.exports = new Class
 	 * @return {Array} An array with the names of the found parameters
 	 */
 	,findHolders: function () {}
+	
+	/**
+	 * Renders the object as an SQL string.
+	 *
+	 * @param {Object} params The params used to render the object
+	 * @return {string} The SQL string
+	 */
+	,render: function () {}
+	
+	/**
+	 * Renders an objects array.
+	 *
+	 * @param {Array} list The objects array
+	 * @param {Object} params The parameters
+	 * @return {string} The rendered SQL string
+	 */
+	,renderList: function (list, params)
+	{
+		var sql = '';
+
+		list.forEach (function (item) {
+			sql += item.render (params);
+		})
+
+		return sql;
+	}
+
+	/**
+	 * Renders an objects array using a separator.
+	 *
+	 * @param {Array} list The objects array
+	 * @param {Object} params The parameters
+	 * @param {String} separator The separator between items
+	 * @return {string} The rendered SQL string
+	 */
+	,renderListWs: function (list, params, separator)
+	{
+		var sql = '';
+
+		list.forEach (function (item, i) {
+			if (i > 0)
+				sql += separator;
+			sql += item.render (params);
+		})
+
+		return sql;
+	}
+
+	/**
+	 * Renders a quoted SQL identifier.
+	 *
+	 * @param {String} identifier The identifier
+	 * @return {string} The quoted identifier
+	 */
+	,renderIdentifier: function (identifier)
+	{
+		return '`'+ identifier +'`';
+	}
+
+	/**
+	 * Renders the object if it's defined.
+	 *
+	 * @param {String} prefix The rendered string prefix
+	 * @return {string} The rendered object with its prefix
+	 */
+	,renderIfSet: function (object, prefix, params)
+	{
+		if (object)
+			return ' '+ prefix +' '+ object.render (params);
+		else
+			return '';
+	}
 });
diff --git a/js/sql/operation.js b/js/sql/operation.js
index 76445641..5f8284fb 100644
--- a/js/sql/operation.js
+++ b/js/sql/operation.js
@@ -1,11 +1,12 @@
 
 var Expr = require ('./expr');
+var ListHolder = require ('./list-holder');
 
 /**
  * The equivalent of a SQL operation between exprs.
  *
- * @param {Array#Sql.Expr} expr Array with the exprs
- * @param {Sql..Operation.Type} type The type of the operation
+ * @param {Array#Expr} exprs Array with the exprs
+ * @param {Type} type The type of the operation
  */
 var Klass = new Class ();
 module.exports = Klass;
@@ -37,6 +38,7 @@ Klass.extend
 Klass.implement
 ({
 	Extends: Expr
+	,Implements: ListHolder
 	,Tag: 'sql-operation'
 	,Properties:
 	{
@@ -44,47 +46,26 @@ Klass.implement
 		{
 			enumType: Type
 			,value: -1
-		}
-	}
-
-	,initialize: function (props)
-	{
-		this.parent (props);
-		this.link ({exprs: new Sql.List ()}, {'changed': this.onListChange});
-	}
-	
-	,appendChild: function (child)
-	{
-		this.exprs.add (child);
-	}
-
-	,onListChange: function ()
-	{
-		this.emit ('changed');
-	}
-
-	,isReady: function ()
-	{	
-		return this.exprs.isReady ();
-	}
-
-	,render: function (batch)
-	{
-		var sql = '(';
-		var operator = ' '+ Operators[this.type] +' ';
-		var e = this.exprs.getArray ();
-
-		for (var i = 0; i < e.length; i++)
+		},
+		exprs:
 		{
-			if (i > 0)
-				sql += operator;
-
-			sql += e[i].render (batch);
+			type: Array
+			,set: function (x)
+			{
+				this.list = x;
+			}
+			,get: function ()
+			{
+				return this.list;
+			}
 		}
-		
-		sql += ')';
-		
-		return sql;
+	}
+
+	,render: function (params)
+	{
+		var operator = ' '+ Operators[this.type] +' ';
+		return '('
+			+ this.renderListWs (this.list, params, operator)
+			+ ')';
 	}
 });
-
diff --git a/js/sql/select.js b/js/sql/select.js
index 4a0db861..37fbb5b1 100644
--- a/js/sql/select.js
+++ b/js/sql/select.js
@@ -16,22 +16,17 @@ module.exports = new Class
 		this.expr.push (new Field ({name: fieldName}));
 	}
 
-	,render: function (batch)
+	,render: function (params)
 	{
 		var sql = 'SELECT '
-		
-		for (var i = 0; i < this.expr.length; i++)
-		{
-			if (i > 0)
-				sql += ', ';
-			sql += this.expr[i].render(batch);
-		}
-		
-		sql += ' FROM ' + this.renderTarget (batch);
+			+ this.renderListWs (this.expr, params, ', ')
+			+ ' FROM '
+			+ this.renderTarget (params);
 		
 		if (this.where)
-			sql += ' WHERE ' + this.where.render (batch);
+			sql += ' WHERE ' + this.where.render (params);
 		
+		sql += this.renderLimit (params);
 		return sql;
 	}
 });
diff --git a/js/sql/sql.js b/js/sql/sql.js
index 711d4c4c..04d580b0 100644
--- a/js/sql/sql.js
+++ b/js/sql/sql.js
@@ -5,6 +5,7 @@ Sql = module.exports = {
 	 Object     : require ('./object')
 	,Holder     : require ('./holder')
 	,List       : require ('./list')
+	,ListHolder : require ('./list-holder')
 	,Expr       : require ('./expr')
 	,Value      : require ('./value')
 	,Field      : require ('./field')
@@ -12,6 +13,8 @@ Sql = module.exports = {
 	,Operation  : require ('./operation')
 	,Target     : require ('./target')
 	,Table      : require ('./table')
+	,Join       : require ('./join')
+	,JoinItem   : require ('./join-item')
 	,Stmt       : require ('./stmt')
 	,Dml        : require ('./dml')
 	,String     : require ('./string')
diff --git a/js/sql/stmt.js b/js/sql/stmt.js
index e447db74..e1d4f1d1 100644
--- a/js/sql/stmt.js
+++ b/js/sql/stmt.js
@@ -14,6 +14,11 @@ module.exports = new Class
 		{
 			type: Expr
 			,value: null
+		},
+		limit:
+		{
+			type: Number
+			,value: null
 		}
 	}
 
@@ -24,24 +29,19 @@ module.exports = new Class
 		this.target.push (target);
 	}
 
-	,renderTarget: function (batch)
+	,renderTarget: function (params)
 	{
-		var sql;
-		var len = this.target.length;
-		
-		if (len > 0)
-		{
-			sql = ' ';
-		
-			for (var n = 0; n < len; n++)
-			{
-				if (n > 0) sql += ', ';
-				sql += this.target[n].render (batch);
-			}
-		}
+		if (this.target.length > 0)
+			return this.renderListWs (this.target, params, ', ');
 		else
-			sql += 'DUAL';
-		
-		return sql;
-	}		
+			return 'DUAL';
+	}
+	
+	,renderLimit: function ()
+	{
+		if (this.limit != null)
+			return 'LIMIT '+ parseInt (this.limit);
+		else
+			return '';
+	}
 });
diff --git a/js/sql/table.js b/js/sql/table.js
index ca783387..82c141c9 100644
--- a/js/sql/table.js
+++ b/js/sql/table.js
@@ -14,6 +14,11 @@ module.exports = new Class
 			type: String
 			,value: null
 		},
+		alias:
+		{
+			type: String
+			,value: null
+		},
 		schema:
 		{
 			type: String
@@ -21,10 +26,18 @@ module.exports = new Class
 		}
 	}
 
-	,render: function (batch)
+	,render: function ()
 	{
-		var sql = this.schema ? '`' + this.schema + '`.' : '';
-		sql += '`' +  this.name + '`';
+		var sql = '';
+
+		if (this.schema)
+			sql += this.renderIndentifier (this.schema) +'.';
+
+		sql += this.renderIndentifier (this.name);
+
+		if (this.alias)
+			sql += ' AS '+ this.renderIndentifier (this.alias);
+
 		return sql;
 	}
 });
diff --git a/js/sql/update.js b/js/sql/update.js
index 89d4ae8e..8293fb15 100644
--- a/js/sql/update.js
+++ b/js/sql/update.js
@@ -8,25 +8,22 @@ module.exports = new Class
 ({
 	Extends: Dml
 
-	,render: function (batch)
+	,render: function (params)
 	{
-		var sql;
-		var n;
+		var sql = 'UPDATE '
+			+ this.renderTarget (params)
+			+ ' SET ';
 		
-		sql = 'UPDATE ' + this.renderTarget (batch) + ' SET ';
-		
-		for (n = 0; n < this.field.length; n++)
-		{
-			if (n > 0)
+		this.field.forEach (function (field, i) {
+			if (i > 0)
 				sql += ', ';
-			sql += this.field[n].render () + ' = ' + this.expr[n].render(batch);
-		}
+			sql += field.render (params) 
+				+ ' = '
+				+ this.expr[i].render(params);
+		});
 		
-		if (this.where)
-			sql += ' WHERE ' + this.where.render (batch);
-			
-		sql += ' LIMIT 1';	// Only for security.
-			
+		sql += this.renderIfSet (this.where, 'WHERE', params)
+			+ this.renderLimit(params);
 		return sql;
 	}
 });
diff --git a/js/vn/lot-query.js b/js/vn/lot-query.js
index c4248a18..34e5739d 100644
--- a/js/vn/lot-query.js
+++ b/js/vn/lot-query.js
@@ -76,7 +76,18 @@ module.exports = new Class
 	{
 		params = this.transformParams (params);
 		var diff = Value.partialDiff (this._params, params);
+		this._assign (diff);
+	}
 
+	,setAll: function (params)
+	{
+		params = this.transformParams (params);
+		var diff = Value.diff (this._params, params);
+		this._assign (diff);
+	}
+
+	,_assign: function (diff)
+	{
 		if (diff)
 		{
 			Object.assign (this._params, diff);
@@ -93,11 +104,6 @@ module.exports = new Class
 		}
 	}
 
-	,setAll: function (params)
-	{
-		this.assign (params);
-	}
-
 	,transformParams: function (params)
 	{
 		var newParams = {};
@@ -125,6 +131,8 @@ function cast (value, type)
 			var date = new Date (value);
 			date.setHours (0, 0, 0, 0);
 			return date;
+		case String:
+			return value;
 		default:
 		if (type instanceof Object)
 			return JSON.parse (value);
diff --git a/js/vn/lot.js b/js/vn/lot.js
index a7bf294a..ab4f417c 100644
--- a/js/vn/lot.js
+++ b/js/vn/lot.js
@@ -50,22 +50,20 @@ module.exports = new Class
 	,assign: function (params)
 	{
 		var diff = Value.partialDiff (this._params, params);
-
-		if (diff)
-		{
-			Object.assign (this._params, diff);
-			this._paramsChanged (diff);
-			this.changed (diff);
-		}
+		this._assign (diff);
 	}
 
 	,setAll: function (params)
 	{
 		var diff = Value.diff (this._params, params);
+		this._assign (diff);
+	}
 
+	,_assign: function (diff)
+	{
 		if (diff)
 		{
-			this._params = Value.kvClone (params);
+			Object.assign (this._params, diff);
 			this._paramsChanged (diff);
 			this.changed (diff);
 		}