Repository: genjidb/genji (go)

Issue:
Add type aliases
Supported types in Genji are the following:
- `INTEGER`
- `DOUBLE`
- `BOOL`
- `BLOB`
- `TEXT`
- `ARRAY`
- `DOCUMENT`

Add type aliases allowing to refer to any of these types using this list:

Types referring to `INTEGER`:
- `INT`
- `TINYINT`
- `SMALLINT`
- `MEDIUMINT`
- `BIGINT`
- `INT2`
- `INT8`

Types referring to `DOUBLE`:
- `DOUBLE PRECISION`
- `REAL`

Types referring to `TEXT`:
- `CHARACTER(n)`
- `VARCHAR(n)`

Any of these types, when parsed, must actually refer to the corresponding concrete type.

Pull request:
Types Aliases
Fixes #183 

Add all Aliases types and tests for this types in different queries.

Patch:
diff --git a/document/value.go b/document/value.go
index 1d80ebff..47e9aefb 100644
--- a/document/value.go
+++ b/document/value.go
@@ -250,10 +250,15 @@ func (v Value) MarshalJSON() ([]byte, error) {
 		return strconv.AppendInt(nil, v.V.(int64), 10), nil
 	case DoubleValue:
 		f := v.V.(float64)
-		if f == float64(int(f)) {
-			return strconv.AppendFloat(nil, f, 'f', 1, 64), nil
+		abs := math.Abs(f)
+		fmt := byte('f')
+		if abs != 0 {
+			if abs < 1e-6 || abs >= 1e21 {
+				fmt = 'e'
+			}
 		}
-		return strconv.AppendFloat(nil, f, 'f', -1, 64), nil
+
+		return strconv.AppendFloat(nil, v.V.(float64), fmt, -1, 64), nil
 	case TextValue:
 		return []byte(strconv.Quote(v.V.(string))), nil
 	case BlobValue:
diff --git a/sql/parser/create.go b/sql/parser/create.go
index 792e1339..6654990a 100644
--- a/sql/parser/create.go
+++ b/sql/parser/create.go
@@ -94,7 +94,10 @@ func (p *Parser) parseFieldConstraints(info *database.TableInfo) error {
 			break
 		}
 
-		fc.Type = p.parseType()
+		fc.Type, err = p.parseType()
+		if err != nil {
+			return err
+		}
 
 		err = p.parseFieldConstraint(&fc)
 		if err != nil {
diff --git a/sql/parser/expr.go b/sql/parser/expr.go
index 0b676235..dd1f830d 100644
--- a/sql/parser/expr.go
+++ b/sql/parser/expr.go
@@ -286,29 +286,52 @@ func (p *Parser) parseParam() (expr.Expr, error) {
 	}
 }
 
-func (p *Parser) parseType() document.ValueType {
+func (p *Parser) parseType() (document.ValueType, error) {
 	tok, _, _ := p.ScanIgnoreWhitespace()
 	switch tok {
 	case scanner.TYPEARRAY:
-		return document.ArrayValue
+		return document.ArrayValue, nil
 	case scanner.TYPEBLOB:
-		return document.BlobValue
+		return document.BlobValue, nil
 	case scanner.TYPEBOOL:
-		return document.BoolValue
+		return document.BoolValue, nil
 	case scanner.TYPEBYTES:
-		return document.BlobValue
+		return document.BlobValue, nil
 	case scanner.TYPEDOCUMENT:
-		return document.DocumentValue
+		return document.DocumentValue, nil
+	case scanner.TYPEREAL:
+		return document.DoubleValue, nil
 	case scanner.TYPEDOUBLE:
-		return document.DoubleValue
-	case scanner.TYPEINTEGER:
-		return document.IntegerValue
+		tok, _, _ := p.ScanIgnoreWhitespace()
+		if tok == scanner.PRECISION {
+			return document.DoubleValue, nil
+		}
+		p.Unscan()
+		return document.DoubleValue, nil
+	case scanner.TYPEINTEGER, scanner.TYPEINT, scanner.TYPEINT2, scanner.TYPEINT8, scanner.TYPETINYINT,
+		 scanner.TYPEBIGINT, scanner.TYPEMEDIUMINT, scanner.TYPESMALLINT:
+		return document.IntegerValue, nil
 	case scanner.TYPETEXT:
-		return document.TextValue
+		return document.TextValue, nil
+	case scanner.TYPEVARCHAR, scanner.TYPECHARACTER:
+		if tok, pos, lit := p.ScanIgnoreWhitespace(); tok != scanner.LPAREN {
+			return 0, newParseError(scanner.Tokstr(tok, lit), []string{"("}, pos)
+		}
+		
+		// The value between parentheses is not used.
+		if tok, pos, lit := p.ScanIgnoreWhitespace(); tok != scanner.INTEGER {
+			return 0, newParseError(scanner.Tokstr(tok, lit), []string{"integer"}, pos)
+		}
+
+		if tok, pos, lit := p.ScanIgnoreWhitespace(); tok != scanner.RPAREN {
+			return 0, newParseError(scanner.Tokstr(tok, lit), []string{")"}, pos)
+		}
+
+		return document.TextValue, nil
 	}
 
 	p.Unscan()
-	return 0
+	return 0, nil
 }
 
 // parseDocument parses a document
@@ -545,7 +568,11 @@ func (p *Parser) parseCastExpression() (expr.Expr, error) {
 	}
 
 	// Parse require typename.
-	tp := p.parseType()
+	tp, err := p.parseType()
+	if err != nil {
+		return nil, err
+	}
+
 	if tp == 0 {
 		tok, pos, lit := p.ScanIgnoreWhitespace()
 		p.Unscan()
@@ -558,4 +585,4 @@ func (p *Parser) parseCastExpression() (expr.Expr, error) {
 	}
 
 	return expr.CastFunc{Expr: e, CastAs: tp}, nil
-}
+}
\ No newline at end of file
diff --git a/sql/scanner/token.go b/sql/scanner/token.go
index fab98a8e..7f6db186 100644
--- a/sql/scanner/token.go
+++ b/sql/scanner/token.go
@@ -98,6 +98,7 @@ const (
 	ON
 	ONLY
 	ORDER
+	PRECISION
 	PRIMARY
 	READ
 	REINDEX
@@ -115,14 +116,25 @@ const (
 	WHERE
 	WRITE
 
+	// Aliases
 	TYPEARRAY
+	TYPEBIGINT
 	TYPEBLOB
 	TYPEBOOL
 	TYPEBYTES
+	TYPECHARACTER
 	TYPEDOCUMENT
 	TYPEDOUBLE
+	TYPEINT
+	TYPEINT2
+	TYPEINT8
 	TYPEINTEGER
+	TYPEMEDIUMINT
+	TYPESMALLINT
 	TYPETEXT
+	TYPETINYINT
+	TYPEREAL
+	TYPEVARCHAR
 
 	keywordEnd
 )
@@ -204,6 +216,7 @@ var tokens = [...]string{
 	ON:          "ON",
 	ONLY:        "ONLY",
 	ORDER:       "ORDER",
+	PRECISION:   "PRECISION",
 	PRIMARY:     "PRIMARY",
 	READ:        "READ",
 	REINDEX:     "REINDEX",
@@ -221,14 +234,24 @@ var tokens = [...]string{
 	WHERE:       "WHERE",
 	WRITE:       "WRITE",
 
-	TYPEARRAY:    "ARRAY",
-	TYPEBLOB:     "BLOB",
-	TYPEBOOL:     "BOOL",
-	TYPEBYTES:    "BYTES",
-	TYPEDOCUMENT: "DOCUMENT",
-	TYPEDOUBLE:   "DOUBLE",
-	TYPEINTEGER:  "INTEGER",
-	TYPETEXT:     "TEXT",
+	TYPEARRAY:     "ARRAY",
+	TYPEBIGINT:    "BIGINT",
+	TYPEBLOB:      "BLOB",
+	TYPEBOOL:      "BOOL",
+	TYPEBYTES:     "BYTES",
+	TYPECHARACTER: "CHARACTER",
+	TYPEDOCUMENT:  "DOCUMENT",
+	TYPEDOUBLE:    "DOUBLE",
+	TYPEINT:       "INT",
+	TYPEINT2:      "INT2",
+	TYPEINT8:      "INT8",
+	TYPEINTEGER:   "INTEGER",
+	TYPEMEDIUMINT: "MEDIUMINT",
+	TYPESMALLINT:  "SMALLINT",
+	TYPETEXT:      "TEXT",
+	TYPETINYINT:   "TINYINT",
+	TYPEREAL:      "REAL",
+	TYPEVARCHAR:   "VARCHAR",
 }
 
 var keywords map[string]Token


Test patch:
diff --git a/document/value_test.go b/document/value_test.go
index a6fcc514..c0a08a3b 100644
--- a/document/value_test.go
+++ b/document/value_test.go
@@ -21,7 +21,6 @@ func TestValueString(t *testing.T) {
 		{"bool", document.NewBoolValue(true), "true"},
 		{"int", document.NewIntegerValue(10), "10"},
 		{"double", document.NewDoubleValue(10.1), "10.1"},
-		{"double", document.NewDoubleValue(10), "10.0"},
 		{"document", document.NewDocumentValue(document.NewFieldBuffer().Add("a", document.NewIntegerValue(10))), "{\"a\": 10}"},
 		{"array", document.NewArrayValue(document.NewValueBuffer(document.NewIntegerValue(10))), "[10]"},
 	}
diff --git a/sql/parser/create_test.go b/sql/parser/create_test.go
index 5983ece5..de1d5c27 100644
--- a/sql/parser/create_test.go
+++ b/sql/parser/create_test.go
@@ -116,6 +116,58 @@ func TestParserCreateTable(t *testing.T) {
 					},
 				},
 			}, false},
+		{"With integer aliases types",
+			"CREATE TABLE test(i int, ii int2, ei int8, m mediumint, s smallint, b bigint, t tinyint)",
+			query.CreateTableStmt{
+				TableName: "test",
+				Info: database.TableInfo{
+					FieldConstraints: []database.FieldConstraint{
+						{Path: parsePath(t, "i"), Type: document.IntegerValue},
+						{Path: parsePath(t, "ii"), Type: document.IntegerValue},
+						{Path: parsePath(t, "ei"), Type: document.IntegerValue},
+						{Path: parsePath(t, "m"), Type: document.IntegerValue},
+						{Path: parsePath(t, "s"), Type: document.IntegerValue},
+						{Path: parsePath(t, "b"), Type: document.IntegerValue},
+						{Path: parsePath(t, "t"), Type: document.IntegerValue},
+					},
+				},
+			}, false},
+		{"With double aliases types",
+			"CREATE TABLE test(dp DOUBLE PRECISION, r real, d double)",
+			query.CreateTableStmt{
+				TableName: "test",
+				Info: database.TableInfo{
+					FieldConstraints: []database.FieldConstraint{
+						{Path: parsePath(t, "dp"), Type: document.DoubleValue},
+						{Path: parsePath(t, "r"), Type: document.DoubleValue},
+						{Path: parsePath(t, "d"), Type: document.DoubleValue},
+					},
+				},
+			}, false},
+
+		{"With text aliases types",
+			"CREATE TABLE test(v VARCHAR(255), c CHARACTER(64), t TEXT)",
+			query.CreateTableStmt{
+				TableName: "test",
+				Info: database.TableInfo{
+					FieldConstraints: []database.FieldConstraint{
+						{Path: parsePath(t, "v"), Type: document.TextValue},
+						{Path: parsePath(t, "c"), Type: document.TextValue},
+						{Path: parsePath(t, "t"), Type: document.TextValue},
+					},
+				},
+			}, false},
+
+		{"With errored text aliases types",
+			"CREATE TABLE test(v VARCHAR(1 IN [1, 2, 3] AND foo > 4) )",
+			query.CreateTableStmt{
+				TableName: "test",
+				Info: database.TableInfo{
+					FieldConstraints: []database.FieldConstraint{
+						{Path: parsePath(t, "v"), Type: document.TextValue},
+					},
+				},
+			}, true},
 	}
 
 	for _, test := range tests {
diff --git a/sql/query/create_test.go b/sql/query/create_test.go
index eee8028d..95f465f1 100644
--- a/sql/query/create_test.go
+++ b/sql/query/create_test.go
@@ -117,6 +117,45 @@ func TestCreateTable(t *testing.T) {
 			require.NoError(t, err)
 
 		})
+
+		t.Run("with variable aliases data types", func(t *testing.T) {
+			ctx := context.Background()
+
+			err = db.Exec(ctx, `
+				CREATE TABLE test2(
+					foo.bar[1].hello bytes PRIMARY KEY, foo.a[1][2] VARCHAR(255) NOT NULL, bar[4][0].bat tinyint,
+				 	dp double precision, r real, b bigint, m mediumint, eight int8, ii int2, c character(64)
+				)
+			`)
+			require.NoError(t, err)
+
+			err = db.View(func(tx *genji.Tx) error {
+				tb, err := tx.GetTable("test2")
+				if err != nil {
+					return err
+				}
+				info, err := tb.Info()
+				if err != nil {
+					return err
+				}
+
+				require.Equal(t, []database.FieldConstraint{
+					{Path: parsePath(t, "foo.bar[1].hello"), Type: document.BlobValue, IsPrimaryKey: true},
+					{Path: parsePath(t, "foo.a[1][2]"), Type: document.TextValue, IsNotNull: true},
+					{Path: parsePath(t, "bar[4][0].bat"), Type: document.IntegerValue},
+					{Path: parsePath(t, "dp"), Type: document.DoubleValue},
+					{Path: parsePath(t, "r"), Type: document.DoubleValue},
+					{Path: parsePath(t, "b"), Type: document.IntegerValue},
+					{Path: parsePath(t, "m"), Type: document.IntegerValue},
+					{Path: parsePath(t, "eight"), Type: document.IntegerValue},
+					{Path: parsePath(t, "ii"), Type: document.IntegerValue},
+					{Path: parsePath(t, "c"), Type: document.TextValue},
+				}, info.FieldConstraints)
+				return nil
+			})
+			require.NoError(t, err)
+		})
+
 	})
 }
 
diff --git a/sql/query/insert_test.go b/sql/query/insert_test.go
index 895a3178..3a196334 100644
--- a/sql/query/insert_test.go
+++ b/sql/query/insert_test.go
@@ -208,11 +208,45 @@ func TestInsertStmt(t *testing.T) {
 			{"double / not null with type constraint", "DOUBLE NOT NULL", `{}`},
 			{"double / not null with non-respected type constraint ", "DOUBLE NOT NULL", `{a: [1,2,3]}`},
 
+			{"double precision", "DOUBLE PRECISION", `{a: "foo"}`},
+			{"double precision / not null with type constraint", "DOUBLE PRECISION NOT NULL", `{}`},
+			{"double precision / not null with non-respected type constraint ", "DOUBLE PRECISION NOT NULL", `{a: [1,2,3]}`},
+
+			{"real", "REAL", `{a: "foo"}`},
+			{"real / not null with type constraint", "REAL NOT NULL", `{}`},
+			{"real / not null with non-respected type constraint ", "REAL NOT NULL", `{a: [1,2,3]}`},
+
 			{"integer", "INTEGER", `{a: "foo"}`},
 			{"integer / not null with type constraint", "INTEGER NOT NULL", `{}`},
 			{"integer / not null with non-respected type constraint ", "INTEGER NOT NULL", `{a: [1,2,3]}`},
 
+			{"int2", "INT2", `{a: "foo"}`},
+			{"int2 / not null with type constraint", "INT2 NOT NULL", `{}`},
+			{"int2 / not null with non-respected type constraint ", "INT NOT NULL", `{a: [1,2,3]}`},
+
+			{"int8", "INT8", `{a: "foo"}`},
+			{"int8 / not null with type constraint", "INT8 NOT NULL", `{}`},
+			{"int8 / not null with non-respected type constraint ", "INT8 NOT NULL", `{a: [1,2,3]}`},
+
+			{"tinyint", "TINYINT", `{a: "foo"}`},
+			{"tinyint / not null with type constraint", "TINYINT NOT NULL", `{}`},
+			{"tinyint / not null with non-respected type constraint ", "TINYINT NOT NULL", `{a: [1,2,3]}`},
+
+			{"bigint", "BIGINT", `{a: "foo"}`},
+			{"bigint / not null with type constraint", "BIGINT NOT NULL", `{}`},
+			{"bigint / not null with non-respected type constraint ", "BIGINT NOT NULL", `{a: [1,2,3]}`},
+
+			{"smallint", "SMALLINT", `{a: "foo"}`},
+			{"smallint / not null with type constraint", "SMALLINT NOT NULL", `{}`},
+			{"smallint / not null with non-respected type constraint ", "SMALLINT NOT NULL", `{a: [1,2,3]}`},
+
+			{"mediumint", "MEDIUMINT", `{a: "foo"}`},
+			{"mediumint / not null with type constraint", "MEDIUMINT NOT NULL", `{}`},
+			{"mediumint / not null with non-respected type constraint ", "MEDIUMINT NOT NULL", `{a: [1,2,3]}`},
+
 			{"text / not null with type constraint", "TEXT NOT NULL", `{}`},
+			{"varchar / not null with type constraint", "VARCHAR(255) NOT NULL", `{}`},
+			{"character / not null with type constraint", "CHARACTER(64) NOT NULL", `{}`},
 		}
 
 		db, err := genji.Open(":memory:")