Skip to content
This repository was archived by the owner on Dec 24, 2022. It is now read-only.

Commit 4b6489e

Browse files
madalenomythz
authored andcommitted
FireBird 4 support (#628)
* Added support for FireBird 4. Fixed DoesTableExist() that was returning null if table didn't exist. Added support for the new FireBird 4 Identity autoincrement columns. Added some tests for the new FirdBird 4 features. Changed behavior of AutoIncrement fields to use Identity instead of Sequences when using the new FireBird 4 dialect. Work in progress,,,, * Fixed PopulateReturnValues() when the primary key is a Guid (attribute AutoId). It tried to convert the value to an Int64. Fixed PrepareParameterizedInsertStatement() in Firebird4OrmLiteDialectProvider to generate the insert sql with GEN_UUID() for the fields with AutoId attribute when using the FirebirdCompactGuidConverter. Fixed ShouldSkipInsert() in FirebirdOrmLiteDialectProvider to not skip fields with AutoId attribute. Added some tests for models with AutId attributes.
1 parent 820acdf commit 4b6489e

File tree

14 files changed

+767
-71
lines changed

14 files changed

+767
-71
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System;
2+
using ServiceStack.Text;
3+
4+
namespace ServiceStack.OrmLite.Firebird
5+
{
6+
public class Firebird4NamingStrategy : FirebirdNamingStrategy
7+
{
8+
public Firebird4NamingStrategy() : base(63) { }
9+
}
10+
}
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Data;
4+
using System.Reflection;
5+
using System.Text;
6+
using System.Linq;
7+
using FirebirdSql.Data.FirebirdClient;
8+
using ServiceStack.DataAnnotations;
9+
using ServiceStack.OrmLite;
10+
using ServiceStack.OrmLite.Firebird.Converters;
11+
using ServiceStack.Text;
12+
13+
namespace ServiceStack.OrmLite.Firebird
14+
{
15+
public class Firebird4OrmLiteDialectProvider : FirebirdOrmLiteDialectProvider
16+
{
17+
private readonly bool usesCompactGuid;
18+
19+
public static List<string> RESERVED = new List<string>(new[] {
20+
"USER","ORDER","PASSWORD", "ACTIVE","LEFT","DOUBLE", "FLOAT", "DECIMAL","STRING", "DATE","DATETIME", "TYPE","TIMESTAMP",
21+
"INDEX","UNIQUE", "PRIMARY", "KEY", "ALTER", "DROP", "CREATE", "DELETE", "VALUES", "FUNCTION", "INT", "LONG", "CHAR", "VALUE", "TIME"
22+
});
23+
24+
public new static Firebird4OrmLiteDialectProvider Instance = new Firebird4OrmLiteDialectProvider();
25+
26+
public Firebird4OrmLiteDialectProvider() : this(true) { }
27+
28+
public Firebird4OrmLiteDialectProvider(bool compactGuid): base(compactGuid)
29+
{
30+
usesCompactGuid = compactGuid;
31+
32+
// FB4 now has identity columns
33+
base.AutoIncrementDefinition = " GENERATED BY DEFAULT AS IDENTITY ";
34+
NamingStrategy = new Firebird4NamingStrategy();
35+
36+
base.RemoveConverter<bool>();
37+
38+
this.Variables = new Dictionary<string, string>
39+
{
40+
{ OrmLiteVariables.SystemUtc, "CURRENT_TIMESTAMP" },
41+
{ OrmLiteVariables.MaxText, "VARCHAR(1000)" },
42+
{ OrmLiteVariables.MaxTextUnicode, "VARCHAR(2048)" },
43+
{ OrmLiteVariables.True, SqlBool(true) },
44+
{ OrmLiteVariables.False, SqlBool(false) },
45+
};
46+
}
47+
48+
public override string ToCreateTableStatement(Type tableType)
49+
{
50+
var sbColumns = StringBuilderCache.Allocate();
51+
var sbConstraints = StringBuilderCacheAlt.Allocate();
52+
53+
var sbPk = new StringBuilder();
54+
55+
var modelDef = GetModel(tableType);
56+
foreach (var fieldDef in CreateTableFieldsStrategy(modelDef))
57+
{
58+
if (fieldDef.CustomSelect != null)
59+
continue;
60+
61+
if (fieldDef.IsPrimaryKey)
62+
sbPk.AppendFormat(sbPk.Length != 0 ? ",{0}" : "{0}", GetQuotedColumnName(fieldDef.FieldName));
63+
64+
if (sbColumns.Length != 0)
65+
sbColumns.Append(", \n ");
66+
67+
var columnDefinition = GetColumnDefinition(fieldDef);
68+
sbColumns.Append(columnDefinition);
69+
70+
if (fieldDef.ForeignKey == null || OrmLiteConfig.SkipForeignKeys)
71+
continue;
72+
73+
var refModelDef = GetModel(fieldDef.ForeignKey.ReferenceType);
74+
75+
var fkName = NamingStrategy.ApplyNameRestrictions(fieldDef.ForeignKey.GetForeignKeyName(modelDef, refModelDef, NamingStrategy, fieldDef)).ToLower();
76+
sbConstraints.AppendFormat(", \n\n CONSTRAINT {0} FOREIGN KEY ({1}) REFERENCES {2} ({3})",
77+
GetQuotedName(fkName),
78+
GetQuotedColumnName(fieldDef.FieldName),
79+
GetQuotedTableName(refModelDef),
80+
GetQuotedColumnName(refModelDef.PrimaryKey.FieldName));
81+
82+
sbConstraints.Append(GetForeignKeyOnDeleteClause(fieldDef.ForeignKey));
83+
sbConstraints.Append(GetForeignKeyOnUpdateClause(fieldDef.ForeignKey));
84+
}
85+
86+
if (sbPk.Length != 0)
87+
sbColumns.AppendFormat(", \n PRIMARY KEY({0})", sbPk);
88+
89+
var sql = $"RECREATE TABLE {GetQuotedTableName(modelDef)} \n(\n {StringBuilderCache.ReturnAndFree(sbColumns)}{StringBuilderCacheAlt.ReturnAndFree(sbConstraints)} \n); \n";
90+
91+
return sql;
92+
}
93+
94+
public override List<string> ToCreateSequenceStatements(Type tableType)
95+
{
96+
var gens = new List<string>();
97+
var modelDef = GetModel(tableType);
98+
99+
foreach (var fieldDef in modelDef.FieldDefinitions)
100+
{
101+
if (!fieldDef.AutoIncrement || fieldDef.Sequence.IsNullOrEmpty()) continue;
102+
103+
// https://firebirdsql.org/refdocs/langrefupd21-ddl-sequence.html
104+
var sequence = Sequence(modelDef.ModelName, fieldDef.FieldName, fieldDef.Sequence).ToUpper();
105+
gens.Add(GetCreateSequenceSql(sequence));
106+
}
107+
return gens;
108+
}
109+
110+
protected override void EnsureAutoIncrementSequence(ModelDefinition modelDef, FieldDefinition fieldDef)
111+
{
112+
if (fieldDef.AutoIncrement && !string.IsNullOrEmpty(fieldDef.Sequence))
113+
{
114+
fieldDef.Sequence = Sequence(modelDef.ModelName, fieldDef.FieldName, fieldDef.Sequence);
115+
}
116+
}
117+
118+
public override string GetColumnDefinition(FieldDefinition fieldDef)
119+
{
120+
var fieldDefinition = ResolveFragment(fieldDef.CustomFieldDefinition)
121+
?? GetColumnTypeDefinition(fieldDef.ColumnType, fieldDef.FieldLength, fieldDef.Scale);
122+
123+
var sql = StringBuilderCache.Allocate();
124+
sql.AppendFormat("{0} {1}", GetQuotedColumnName(fieldDef.FieldName), fieldDefinition);
125+
126+
var defaultValue = GetDefaultValue(fieldDef);
127+
if (fieldDef.IsRowVersion)
128+
{
129+
sql.AppendFormat(DefaultValueFormat, 1L);
130+
}
131+
else if (!string.IsNullOrEmpty(defaultValue))
132+
{
133+
sql.AppendFormat(DefaultValueFormat, defaultValue);
134+
}
135+
136+
if (fieldDef.AutoIncrement && string.IsNullOrEmpty(fieldDef.Sequence))
137+
{
138+
sql.Append(AutoIncrementDefinition);
139+
}
140+
else
141+
// Identity columns must accept null to generate a new value.
142+
if (!fieldDef.IsNullable)
143+
{
144+
sql.Append(" NOT NULL");
145+
}
146+
if (fieldDef.IsUniqueConstraint)
147+
{
148+
sql.Append(" UNIQUE");
149+
}
150+
151+
return StringBuilderCacheAlt.ReturnAndFree(sql);
152+
}
153+
154+
public override void PrepareParameterizedInsertStatement<T>(IDbCommand cmd, ICollection<string> insertFields = null)
155+
{
156+
var sbColumnNames = StringBuilderCache.Allocate();
157+
var sbColumnValues = StringBuilderCacheAlt.Allocate();
158+
var sbReturningColumns = StringBuilderCacheAlt.Allocate();
159+
var modelDef = OrmLiteUtils.GetModelDefinition(typeof(T));
160+
161+
cmd.Parameters.Clear();
162+
cmd.CommandTimeout = OrmLiteConfig.CommandTimeout;
163+
164+
var fieldDefs = GetInsertFieldDefinitions(modelDef, insertFields);
165+
foreach (var fieldDef in fieldDefs)
166+
{
167+
if (ShouldReturnOnInsert(modelDef, fieldDef))
168+
{
169+
if (sbReturningColumns.Length > 0)
170+
sbReturningColumns.Append(",");
171+
sbReturningColumns.Append(GetQuotedColumnName(fieldDef.FieldName));
172+
}
173+
174+
if (ShouldSkipInsert(fieldDef) || (fieldDef.AutoIncrement && string.IsNullOrEmpty(fieldDef.Sequence)))
175+
continue;
176+
177+
if (sbColumnNames.Length > 0)
178+
sbColumnNames.Append(",");
179+
if (sbColumnValues.Length > 0)
180+
sbColumnValues.Append(",");
181+
182+
try
183+
{
184+
sbColumnNames.Append(GetQuotedColumnName(fieldDef.FieldName));
185+
186+
// in FB4 only use 'next value for' if the fielddef has a sequence explicitly.
187+
if (fieldDef.AutoIncrement && !string.IsNullOrEmpty(fieldDef.Sequence))
188+
{
189+
EnsureAutoIncrementSequence(modelDef, fieldDef);
190+
sbColumnValues.Append("NEXT VALUE FOR " + fieldDef.Sequence);
191+
}
192+
if (fieldDef.AutoId && usesCompactGuid)
193+
{
194+
sbColumnValues.Append("GEN_UUID()");
195+
}
196+
else
197+
{
198+
sbColumnValues.Append(this.GetParam(SanitizeFieldNameForParamName(fieldDef.FieldName)));
199+
AddParameter(cmd, fieldDef);
200+
}
201+
}
202+
catch (Exception ex)
203+
{
204+
Log.Error("ERROR in PrepareParameterizedInsertStatement(): " + ex.Message, ex);
205+
throw;
206+
}
207+
}
208+
209+
var strReturning = StringBuilderCacheAlt.ReturnAndFree(sbReturningColumns);
210+
cmd.CommandText = string.Format("INSERT INTO {0} ({1}) VALUES ({2}) {3}",
211+
GetQuotedTableName(modelDef),
212+
StringBuilderCache.ReturnAndFree(sbColumnNames),
213+
StringBuilderCacheAlt.ReturnAndFree(sbColumnValues),
214+
strReturning.Length > 0 ? "RETURNING " + strReturning : "");
215+
}
216+
}
217+
}
218+

src/ServiceStack.OrmLite.Firebird/FirebirdDialect.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,10 @@ public static class FirebirdDialect
77
public static IOrmLiteDialectProvider Provider => FirebirdOrmLiteDialectProvider.Instance;
88
public static FirebirdOrmLiteDialectProvider Instance => FirebirdOrmLiteDialectProvider.Instance;
99
}
10+
11+
public static class Firebird4Dialect
12+
{
13+
public static IOrmLiteDialectProvider Provider => Firebird4OrmLiteDialectProvider.Instance;
14+
public static Firebird4OrmLiteDialectProvider Instance => Firebird4OrmLiteDialectProvider.Instance;
15+
}
1016
}

src/ServiceStack.OrmLite.Firebird/FirebirdOrmLiteDialectProvider.cs

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ namespace ServiceStack.OrmLite.Firebird
1414
{
1515
public class FirebirdOrmLiteDialectProvider : OrmLiteDialectProviderBase<FirebirdOrmLiteDialectProvider>
1616
{
17+
protected virtual string GetCreateSequenceSql(string sequence) => $@"
18+
EXECUTE BLOCK AS BEGIN
19+
if (not exists(select 1 FROM RDB$GENERATORS WHERE RDB$GENERATOR_NAME = '{sequence}')) then
20+
begin
21+
execute statement 'CREATE SEQUENCE {sequence};';
22+
end
23+
END";
24+
1725
public static List<string> RESERVED = new List<string>(new[] {
1826
"USER","ORDER","PASSWORD", "ACTIVE","LEFT","DOUBLE", "FLOAT", "DECIMAL","STRING", "DATE","DATETIME", "TYPE","TIMESTAMP",
1927
"INDEX","UNIQUE", "PRIMARY", "KEY", "ALTER", "DROP", "CREATE", "DELETE", "VALUES", "FUNCTION", "INT", "LONG", "CHAR", "VALUE", "TIME"
@@ -120,21 +128,20 @@ public override string ToInsertRowStatement(IDbCommand cmd, object objWithProper
120128
var sbColumnNames = StringBuilderCache.Allocate();
121129
var sbColumnValues = StringBuilderCacheAlt.Allocate();
122130
var sbReturningColumns = StringBuilderCacheAlt.Allocate();
123-
124131
var tableType = objWithProperties.GetType();
125132
var modelDef = GetModel(tableType);
126133

127134
var fieldDefs = GetInsertFieldDefinitions(modelDef, insertFields);
128135
foreach (var fieldDef in fieldDefs)
129136
{
130-
if (fieldDef.ReturnOnInsert || (fieldDef.IsPrimaryKey && fieldDef.AutoIncrement && HasInsertReturnValues(modelDef)))
137+
if (ShouldReturnOnInsert(modelDef, fieldDef))
131138
{
132139
if (sbReturningColumns.Length > 0)
133140
sbReturningColumns.Append(",");
134141
sbReturningColumns.Append(GetQuotedColumnName(fieldDef.FieldName));
135142
}
136143

137-
if (fieldDef.IsComputed)
144+
if (ShouldSkipInsert(fieldDef))
138145
continue;
139146

140147
if ((fieldDef.AutoIncrement || !string.IsNullOrEmpty(fieldDef.Sequence)
@@ -179,13 +186,22 @@ public override string ToInsertRowStatement(IDbCommand cmd, object objWithProper
179186
return sql;
180187
}
181188

182-
private void EnsureAutoIncrementSequence(ModelDefinition modelDef, FieldDefinition fieldDef)
189+
protected virtual void EnsureAutoIncrementSequence(ModelDefinition modelDef, FieldDefinition fieldDef)
183190
{
184191
if (fieldDef.AutoIncrement && string.IsNullOrEmpty(fieldDef.Sequence))
185192
{
186193
fieldDef.Sequence = Sequence(modelDef.ModelName, fieldDef.FieldName, fieldDef.Sequence);
187194
}
188195
}
196+
197+
protected override bool ShouldSkipInsert(FieldDefinition fieldDef) =>
198+
fieldDef.ShouldSkipInsert() || fieldDef.IsComputed;
199+
200+
protected virtual bool ShouldReturnOnInsert(ModelDefinition modelDef, FieldDefinition fieldDef) =>
201+
fieldDef.ReturnOnInsert || (fieldDef.IsPrimaryKey && fieldDef.AutoIncrement && HasInsertReturnValues(modelDef)) || fieldDef.AutoId;
202+
203+
public override bool HasInsertReturnValues(ModelDefinition modelDef) =>
204+
modelDef.FieldDefinitions.Any(x => x.ReturnOnInsert || (x.AutoId && x.FieldType == typeof(Guid)));
189205

190206
public override void PrepareParameterizedInsertStatement<T>(IDbCommand cmd, ICollection<string> insertFields = null)
191207
{
@@ -200,14 +216,14 @@ public override void PrepareParameterizedInsertStatement<T>(IDbCommand cmd, ICol
200216
var fieldDefs = GetInsertFieldDefinitions(modelDef, insertFields);
201217
foreach (var fieldDef in fieldDefs)
202218
{
203-
if (fieldDef.ReturnOnInsert || (fieldDef.IsPrimaryKey && fieldDef.AutoIncrement && HasInsertReturnValues(modelDef)))
219+
if (ShouldReturnOnInsert(modelDef, fieldDef))
204220
{
205221
if (sbReturningColumns.Length > 0)
206222
sbReturningColumns.Append(",");
207223
sbReturningColumns.Append(GetQuotedColumnName(fieldDef.FieldName));
208224
}
209225

210-
if (fieldDef.ShouldSkipInsert() && !fieldDef.AutoIncrement && string.IsNullOrEmpty(fieldDef.Sequence))
226+
if (ShouldSkipInsert(fieldDef) && !fieldDef.AutoIncrement && !fieldDef.AutoId && string.IsNullOrEmpty(fieldDef.Sequence))
211227
continue;
212228

213229
if (sbColumnNames.Length > 0)
@@ -257,7 +273,7 @@ public override void PrepareUpdateRowStatement(IDbCommand dbCmd, object objWithP
257273

258274
foreach (var fieldDef in modelDef.FieldDefinitions)
259275
{
260-
if (fieldDef.IsComputed)
276+
if (fieldDef.IsComputed || fieldDef.IgnoreOnUpdate)
261277
continue;
262278

263279
if ((fieldDef.IsPrimaryKey || fieldDef.Name == OrmLiteConfig.IdField)
@@ -350,13 +366,7 @@ public override List<string> ToCreateSequenceStatements(Type tableType)
350366
{
351367
// https://firebirdsql.org/refdocs/langrefupd21-ddl-sequence.html
352368
var sequence = Sequence(modelDef.ModelName, fieldDef.FieldName, fieldDef.Sequence).ToUpper();
353-
gens.Add($@"
354-
EXECUTE BLOCK AS BEGIN
355-
if (not exists(select 1 FROM RDB$GENERATORS WHERE RDB$GENERATOR_NAME = '{sequence}')) then
356-
begin
357-
execute statement 'CREATE SEQUENCE {sequence};';
358-
end
359-
END");
369+
gens.Add(GetCreateSequenceSql(sequence));
360370
}
361371
}
362372
return gens;
@@ -690,7 +700,7 @@ public override string GetQuotedColumnName(string fieldName)
690700
return Quote(NamingStrategy.GetColumnName(fieldName));
691701
}
692702

693-
private string Sequence(string modelName, string fieldName, string sequence)
703+
protected string Sequence(string modelName, string fieldName, string sequence)
694704
{
695705
return sequence.IsNullOrEmpty()
696706
? Quote(NamingStrategy.GetSequenceName(modelName, fieldName))
@@ -727,7 +737,7 @@ public override bool DoesTableExist(IDbCommand dbCmd, string tableName, string s
727737
// if (!QuoteNames & !RESERVED.Contains(tableName.ToUpper()))
728738
// tableName = tableName.ToUpper();
729739

730-
var sql = $"SELECT 1 FROM rdb$relations WHERE rdb$system_flag = 0 AND rdb$view_blr IS NULL AND rdb$relation_name = '{tableName}'";
740+
var sql = $"SELECT COUNT(*) FROM rdb$relations WHERE rdb$system_flag = 0 AND rdb$view_blr IS NULL AND rdb$relation_name = '{tableName}'";
731741

732742
var result = dbCmd.ExecLongScalar(sql);
733743
return result == 1;

src/ServiceStack.OrmLite/OrmLiteWriteCommandExtensions.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -714,9 +714,11 @@ internal static long PopulateReturnValues<T>(this IDataReader reader, IOrmLiteDi
714714
var values = new object[reader.FieldCount];
715715
var indexCache = reader.GetIndexFieldsCache(modelDef, dialectProvider);
716716
obj.PopulateWithSqlReader(dialectProvider, reader, indexCache, values);
717-
if ((modelDef.PrimaryKey != null) && modelDef.PrimaryKey.AutoIncrement)
717+
if ((modelDef.PrimaryKey != null) && (modelDef.PrimaryKey.AutoIncrement || modelDef.PrimaryKey.ReturnOnInsert))
718718
{
719719
var id = modelDef.GetPrimaryKey(obj);
720+
if (modelDef.PrimaryKey.AutoId)
721+
return 1;
720722
return Convert.ToInt64(id);
721723
}
722724
}

0 commit comments

Comments
 (0)