From ac9c9869140e970e39fee3396621bc52357d033c Mon Sep 17 00:00:00 2001 From: 2881099 <2881099@qq.com> Date: Wed, 28 Jun 2023 16:09:19 +0800 Subject: [PATCH] =?UTF-8?q?-=20=E4=BC=98=E5=8C=96=20QuestDB=20=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E6=98=A0=E5=B0=84=EF=BC=8C=E8=A1=A8=E8=BE=BE=E5=BC=8F?= =?UTF-8?q?=E5=87=BD=E6=95=B0=E8=A7=A3=E6=9E=90=EF=BC=8C=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=20QuestFunc=20=E5=87=BD=E6=95=B0=E8=A7=A3=E6=9E=90=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Examples/base_entity/Program.cs | 30 +- Examples/base_entity/base_entity.csproj | 1 + FreeSql/Internal/UtilsExpressionTree.cs | 8 +- .../QuestDbAdo/QuestDbAdo.cs | 6 +- .../QuestDbCodeFirst.cs | 59 +- .../QuestDbExpression.cs | 558 ++++++------------ .../QuestDbFunctions.cs | 252 ++++++++ .../FreeSql.Provider.QuestDb/QuestDbUtils.cs | 57 +- 8 files changed, 488 insertions(+), 483 deletions(-) create mode 100644 Providers/FreeSql.Provider.QuestDb/QuestDbFunctions.cs diff --git a/Examples/base_entity/Program.cs b/Examples/base_entity/Program.cs index 97e5214f..a23228df 100644 --- a/Examples/base_entity/Program.cs +++ b/Examples/base_entity/Program.cs @@ -590,7 +590,7 @@ namespace base_entity //.UseNameConvert(FreeSql.Internal.NameConvertType.ToUpper) //.UseConnectionString(FreeSql.DataType.OdbcDameng, "Driver={DM8 ODBC DRIVER};Server=127.0.0.1:5236;Persist Security Info=False;Trusted_Connection=Yes;UID=USER1;PWD=123456789") - + .UseConnectionString(DataType.QuestDb, "host=localhost;port=8812;username=admin;password=quest;database=qdb;ServerCompatibilityMode=NoTypeLoading;") .UseMonitorCommand(cmd => { Console.WriteLine(cmd.CommandText + "\r\n"); @@ -602,6 +602,27 @@ namespace base_entity BaseEntity.Initialization(fsql, () => _asyncUow.Value); #endregion + var qr1 = fsql.SelectLongSequence(10, () => new + { + rndstr = QuestFunc.rnd_str(10, 5, 10, 0), + rnddate = QuestFunc.rnd_date(QuestFunc.to_date("2020", "yyyy"), QuestFunc.to_date("2023", "yyyy")) + }) + .From() + .InnerJoin((a, b) => a.rndstr == b.Username).ToList(); + + + var now_to_timezone = ""; + var timeOffset = (int)TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now).TotalMinutes; + if (timeOffset == 0) now_to_timezone = "systimestamp()"; + else + { + var absTimeOffset = Math.Abs(timeOffset); + now_to_timezone = $"to_timezone(systimestamp(),'{(timeOffset > 0 ? '+' : '-')}{(absTimeOffset / 60).ToString().PadLeft(2, '0')}:{(absTimeOffset % 60).ToString().PadLeft(2, '0')}')"; + } + if (now_to_timezone != "to_timezone(systimestamp(),'+08:00')") throw new Exception("questdb DateTime.Now 时差计算错误"); + + + fsql.Insert(new[] { new pgjson_copy001{ json = JObject.FromObject(new {x=1,y=2 })}, @@ -996,7 +1017,12 @@ var sql11111 = fsql.Select() var arguments = callExp.Arguments; var Function = new Func((dbfunc, args, returnType) => { - return $"{dbfunc}({string.Join(", ", args.Select(a => e.FreeParse(a)))})"; + var inputArgs = string.Join(", ", args.Select(a => + { + //var testParse = fsql.Aop.ParseExpressionHandler(fsql, new FreeSql.Aop.ParseExpressionEventArgs(a, e.FreeParse, e.Tables)); + return e.FreeParse(a); + })); + return $"{dbfunc}({inputArgs})"; }); e.Result = callExp.Method.Name switch { diff --git a/Examples/base_entity/base_entity.csproj b/Examples/base_entity/base_entity.csproj index 77d8a921..930f5fcd 100644 --- a/Examples/base_entity/base_entity.csproj +++ b/Examples/base_entity/base_entity.csproj @@ -31,6 +31,7 @@ + diff --git a/FreeSql/Internal/UtilsExpressionTree.cs b/FreeSql/Internal/UtilsExpressionTree.cs index 30d54694..9f76543a 100644 --- a/FreeSql/Internal/UtilsExpressionTree.cs +++ b/FreeSql/Internal/UtilsExpressionTree.cs @@ -526,8 +526,12 @@ namespace FreeSql.Internal { col.Attribute.IsNullable = false; col.Attribute.DbType = col.Attribute.DbType.Replace("NOT NULL", "").Replace(" NULL", "").Trim(); - if (common._orm.Ado.DataType == DataType.Sqlite) - col.Attribute.DbType += " NOT NULL"; //sqlite 主键也可以插入 null + switch(common._orm.Ado.DataType) + { + case DataType.Sqlite: + col.Attribute.DbType += " NOT NULL"; //sqlite 主键也可以插入 null + break; + } } foreach (var col in trytb.Columns.Values) { diff --git a/Providers/FreeSql.Provider.QuestDb/QuestDbAdo/QuestDbAdo.cs b/Providers/FreeSql.Provider.QuestDb/QuestDbAdo/QuestDbAdo.cs index 5d5a955c..aac07991 100644 --- a/Providers/FreeSql.Provider.QuestDb/QuestDbAdo/QuestDbAdo.cs +++ b/Providers/FreeSql.Provider.QuestDb/QuestDbAdo/QuestDbAdo.cs @@ -52,9 +52,11 @@ namespace FreeSql.QuestDb bool isdic; if (param is bool || param is bool?) - return (bool)param; + return (bool)param ? "true" : "false"; else if (param is string) return string.Concat("'", param.ToString().Replace("'", "''"), "'"); + else if (param is Guid || param is Guid?) + return ((Guid)param).ToString("n"); else if (param is char) return string.Concat("'", param.ToString().Replace("'", "''").Replace('\0', ' '), "'"); else if (param is Enum) @@ -64,7 +66,7 @@ namespace FreeSql.QuestDb else if (param is DateTime || param is DateTime?) return string.Concat("'", ((DateTime)param).ToString("yyyy-MM-dd HH:mm:ss.ffffff"), "'"); else if (param is TimeSpan || param is TimeSpan?) - return ((TimeSpan)param).Ticks / 10; + return (long)((TimeSpan)param).TotalSeconds; else if (param is byte[]) return $"'\\x{CommonUtils.BytesSqlRaw(param as byte[])}'"; else if (param is JToken || param is JObject || param is JArray) diff --git a/Providers/FreeSql.Provider.QuestDb/QuestDbCodeFirst.cs b/Providers/FreeSql.Provider.QuestDb/QuestDbCodeFirst.cs index 5b95f039..6c345280 100644 --- a/Providers/FreeSql.Provider.QuestDb/QuestDbCodeFirst.cs +++ b/Providers/FreeSql.Provider.QuestDb/QuestDbCodeFirst.cs @@ -44,62 +44,29 @@ namespace FreeSql.QuestDb { typeof(ushort?).FullName, CsToDb.New(NpgsqlDbType.Integer, "short", "short", false, true, null) }, { typeof(uint).FullName, CsToDb.New(NpgsqlDbType.Bigint, "int", "int NOT NULL", false, false, 0) }, { typeof(uint?).FullName, CsToDb.New(NpgsqlDbType.Bigint, "int", "int", false, true, null) }, - { - typeof(ulong).FullName, CsToDb.New(NpgsqlDbType.Numeric, "long", "long NOT NULL", false, false, 0) - }, - { - typeof(ulong?).FullName, CsToDb.New(NpgsqlDbType.Numeric, "long", "long", false, true, null) - }, + { typeof(ulong).FullName, CsToDb.New(NpgsqlDbType.Numeric, "long", "long NOT NULL", false, false, 0) }, + { typeof(ulong?).FullName, CsToDb.New(NpgsqlDbType.Numeric, "long", "long", false, true, null) }, { typeof(float).FullName, CsToDb.New(NpgsqlDbType.Real, "float", "float NOT NULL", false, false, 0) }, { typeof(float?).FullName, CsToDb.New(NpgsqlDbType.Real, "float", "float", false, true, null) }, { typeof(double).FullName, CsToDb.New(NpgsqlDbType.Double, "double", "double NOT NULL", false, false, 0) }, { typeof(double?).FullName, CsToDb.New(NpgsqlDbType.Double, "double", "double", false, true, null) }, - { - typeof(decimal).FullName, - CsToDb.New(NpgsqlDbType.Numeric, "double", "double NOT NULL", false, false, 0) - }, - { - typeof(decimal?).FullName, - CsToDb.New(NpgsqlDbType.Numeric, "double", "double", false, true, null) - }, + { typeof(decimal).FullName, CsToDb.New(NpgsqlDbType.Numeric, "double", "double NOT NULL", false, false, 0) }, + { typeof(decimal?).FullName, CsToDb.New(NpgsqlDbType.Numeric, "double", "double", false, true, null) }, { typeof(string).FullName, CsToDb.New(NpgsqlDbType.Varchar, "string", "string", false, null, "") }, - { typeof(char).FullName, CsToDb.New(NpgsqlDbType.Char, "char", "char)", false, null, '\0') }, + { typeof(char).FullName, CsToDb.New(NpgsqlDbType.Char, "char", "char", false, null, '\0') }, + { typeof(Guid).FullName, CsToDb.New(NpgsqlDbType.Char, "symbol", "symbol NOT NULL", false, null, Guid.Empty) }, + { typeof(Guid?).FullName, CsToDb.New(NpgsqlDbType.Char, "symbol", "symbol", false, true, null) }, - { - typeof(TimeSpan).FullName, - CsToDb.New(NpgsqlDbType.Time, "timestamp", "timestamp NOT NULL", false, false, 0) - }, - { typeof(TimeSpan?).FullName, CsToDb.New(NpgsqlDbType.Time, "timestamp", "timestamp", false, true, null) }, - { - typeof(DateTime).FullName, - CsToDb.New(NpgsqlDbType.Timestamp, "timestamp", "timestamp NOT NULL", false, false, - new DateTime(1970, 1, 1)) - }, - { - typeof(DateTime?).FullName, - CsToDb.New(NpgsqlDbType.Timestamp, "timestamp", "timestamp", false, true, null) - }, + { typeof(DateTime).FullName, CsToDb.New(NpgsqlDbType.Timestamp, "timestamp", "timestamp NOT NULL", false, false, new DateTime(1970, 1, 1)) }, + { typeof(DateTime?).FullName, CsToDb.New(NpgsqlDbType.Timestamp, "timestamp", "timestamp", false, true, null) }, - { - typeof(bool).FullName, - CsToDb.New(NpgsqlDbType.Boolean, "boolean", "boolean NOT NULL", null, false, false) - }, + { typeof(bool).FullName, CsToDb.New(NpgsqlDbType.Boolean, "boolean", "boolean NOT NULL", null, false, false) }, { typeof(bool?).FullName, CsToDb.New(NpgsqlDbType.Boolean, "boolean", "boolean", null, true, null) }, - //{ typeof(Byte[]).FullName, CsToDb.New(NpgsqlDbType.Bytea, "bytea", "bytea", false, null, new byte[0]) }, - //{ - // typeof(BitArray).FullName, - // CsToDb.New(NpgsqlDbType.Varbit, "varbit", "varbit(64)", false, null, new BitArray(new byte[64])) - //}, - { - typeof(BigInteger).FullName, - CsToDb.New(NpgsqlDbType.Numeric, "long", "long NOT NULL", false, false, 0) - }, - { - typeof(BigInteger?).FullName, - CsToDb.New(NpgsqlDbType.Numeric, "long", "long", false, true, null) - } + { typeof(Byte[]).FullName, CsToDb.New(NpgsqlDbType.Bytea, "binary", "binary", false, null, new byte[0]) }, + { typeof(BigInteger).FullName, CsToDb.New(NpgsqlDbType.Numeric, "long256", "long256 NOT NULL", false, false, 0) }, + { typeof(BigInteger?).FullName, CsToDb.New(NpgsqlDbType.Numeric, "long256", "long256", false, true, null) } }; public override DbInfoResult GetDbInfo(Type type) diff --git a/Providers/FreeSql.Provider.QuestDb/QuestDbExpression.cs b/Providers/FreeSql.Provider.QuestDb/QuestDbExpression.cs index eba578e2..5df75f61 100644 --- a/Providers/FreeSql.Provider.QuestDb/QuestDbExpression.cs +++ b/Providers/FreeSql.Provider.QuestDb/QuestDbExpression.cs @@ -23,11 +23,8 @@ namespace FreeSql.QuestDb { case ExpressionType.ArrayLength: var arrOper = (exp as UnaryExpression)?.Operand; - var arrOperExp = getExp(arrOper); - if (arrOperExp.StartsWith("(") || arrOperExp.EndsWith(")")) - return $"array_length(array[{arrOperExp.TrimStart('(').TrimEnd(')')}],1)"; - if (arrOper.Type == typeof(byte[])) return $"octet_length({getExp(arrOper)})"; - return $"case when {arrOperExp} is null then 0 else array_length({arrOperExp},1) end"; + if (arrOper.Type == typeof(byte[])) return $"length({getExp(arrOper)})"; + break; case ExpressionType.Convert: var operandExp = (exp as UnaryExpression)?.Operand; var gentype = exp.Type.NullableTypeOrThis(); @@ -36,23 +33,23 @@ namespace FreeSql.QuestDb switch (exp.Type.NullableTypeOrThis().ToString()) { case "System.Boolean": - return $"(({getExp(operandExp)})::varchar not in ('0','false','f','no'))"; - case "System.Byte": return $"({getExp(operandExp)})::int2"; - case "System.Char": return $"substr(({getExp(operandExp)})::char, 1, 1)"; + return $"(cast({getExp(operandExp)} as string) not in ('0','false','f','no'))"; + case "System.Byte": return $"cast({getExp(operandExp)} byte)"; + case "System.Char": return $"left(cast({getExp(operandExp)} as string), 1)"; case "System.DateTime": - return ExpressionConstDateTime(operandExp) ?? $"({getExp(operandExp)})::timestamp"; - case "System.Decimal": return $"({getExp(operandExp)})::numeric"; - case "System.Double": return $"({getExp(operandExp)})::float8"; - case "System.Int16": return $"({getExp(operandExp)})::int2"; - case "System.Int32": return $"({getExp(operandExp)})::int4"; - case "System.Int64": return $"({getExp(operandExp)})::int8"; - case "System.SByte": return $"({getExp(operandExp)})::int2"; - case "System.Single": return $"({getExp(operandExp)})::float4"; - case "System.String": return $"({getExp(operandExp)})::text"; - case "System.UInt16": return $"({getExp(operandExp)})::int2"; - case "System.UInt32": return $"({getExp(operandExp)})::int4"; - case "System.UInt64": return $"({getExp(operandExp)})::int8"; - case "System.Guid": return $"({getExp(operandExp)})::uuid"; + return ExpressionConstDateTime(operandExp) ?? $"cast({getExp(operandExp)} as timestamp)"; + case "System.Decimal": return $"cast({getExp(operandExp)} as double)"; + case "System.Double": return $"cast({getExp(operandExp)} as double)"; + case "System.Int16": return $"cast({getExp(operandExp)} as short)"; + case "System.Int32": return $"cast({getExp(operandExp)} as int)"; + case "System.Int64": return $"cast({getExp(operandExp)} as long)"; + case "System.SByte": return $"cast({getExp(operandExp)} as byte)"; + case "System.Single": return $"cast({getExp(operandExp)} as float)"; + case "System.String": return $"cast({getExp(operandExp)} as string)"; + case "System.UInt16": return $"cast({getExp(operandExp)} as short)"; + case "System.UInt32": return $"cast({getExp(operandExp)} as int)"; + case "System.UInt64": return $"cast({getExp(operandExp)} long)"; + case "System.Guid": return $"cast({getExp(operandExp)} symbol)"; } } @@ -67,40 +64,39 @@ namespace FreeSql.QuestDb switch (callExp.Method.DeclaringType.NullableTypeOrThis().ToString()) { case "System.Boolean": - return $"(({getExp(callExp.Arguments[0])})::varchar not in ('0','false','f','no'))"; - case "System.Byte": return $"({getExp(callExp.Arguments[0])})::int2"; - case "System.Char": return $"substr(({getExp(callExp.Arguments[0])})::char, 1, 1)"; + return $"(cast({getExp(callExp.Arguments[0])} as string) not in ('0','false','f','no'))"; + case "System.Byte": return $"cast({getExp(callExp.Arguments[0])} byte)"; + case "System.Char": return $"left(cast({getExp(callExp.Arguments[0])} as string), 1)"; case "System.DateTime": - return ExpressionConstDateTime(callExp.Arguments[0]) ?? - $"({getExp(callExp.Arguments[0])})::timestamp"; - case "System.Decimal": return $"({getExp(callExp.Arguments[0])})::numeric"; - case "System.Double": return $"({getExp(callExp.Arguments[0])})::float8"; - case "System.Int16": return $"({getExp(callExp.Arguments[0])})::int2"; - case "System.Int32": return $"({getExp(callExp.Arguments[0])})::int4"; - case "System.Int64": return $"({getExp(callExp.Arguments[0])})::int8"; - case "System.SByte": return $"({getExp(callExp.Arguments[0])})::int2"; - case "System.Single": return $"({getExp(callExp.Arguments[0])})::float4"; - case "System.UInt16": return $"({getExp(callExp.Arguments[0])})::int2"; - case "System.UInt32": return $"({getExp(callExp.Arguments[0])})::int4"; - case "System.UInt64": return $"({getExp(callExp.Arguments[0])})::int8"; - case "System.Guid": return $"({getExp(callExp.Arguments[0])})::uuid"; + return ExpressionConstDateTime(callExp.Arguments[0]) ?? $"cast({getExp(callExp.Arguments[0])} as timestamp)"; + case "System.Decimal": return $"cast({getExp(callExp.Arguments[0])} as double)"; + case "System.Double": return $"cast({getExp(callExp.Arguments[0])} as double)"; + case "System.Int16": return $"cast({getExp(callExp.Arguments[0])} as short)"; + case "System.Int32": return $"cast({getExp(callExp.Arguments[0])} as int)"; + case "System.Int64": return $"cast({getExp(callExp.Arguments[0])} as long)"; + case "System.SByte": return $"cast({getExp(callExp.Arguments[0])} as byte)"; + case "System.Single": return $"cast({getExp(callExp.Arguments[0])} as float)"; + case "System.String": return $"cast({getExp(callExp.Arguments[0])} as string)"; + case "System.UInt16": return $"cast({getExp(callExp.Arguments[0])} as short)"; + case "System.UInt32": return $"cast({getExp(callExp.Arguments[0])} as int)"; + case "System.UInt64": return $"cast({getExp(callExp.Arguments[0])} long)"; + case "System.Guid": return $"cast({getExp(callExp.Arguments[0])} symbol)"; } - break; case "NewGuid": return null; case "Next": - if (callExp.Object?.Type == typeof(Random)) return "(random()*1000000000)::int4"; + if (callExp.Object?.Type == typeof(Random)) return "rnd_int()"; return null; case "NextDouble": - if (callExp.Object?.Type == typeof(Random)) return "random()"; + if (callExp.Object?.Type == typeof(Random)) return "rnd_double()"; return null; case "Random": - if (callExp.Method.DeclaringType.IsNumberType()) return "random()"; + if (callExp.Method.DeclaringType.IsNumberType()) return "rnd_int()"; return null; case "ToString": if (callExp.Object != null) - return callExp.Arguments.Count == 0 ? $"({getExp(callExp.Object)})::text" : null; + return callExp.Arguments.Count == 0 ? $"cast({getExp(callExp.Object)} as string)" : null; return null; } @@ -121,184 +117,43 @@ namespace FreeSql.QuestDb { case "First": case "FirstOrDefault": - return $"substr({getExp(callExp.Arguments[0])}, 1, 1)"; + return $"left({getExp(callExp.Arguments[0])}, 1)"; } } } - if (objType == null) objType = callExp.Method.DeclaringType; if (objType != null || objType.IsArrayOrList()) { - string left = null; - switch (objType.FullName) - { - case "Newtonsoft.Json.Linq.JToken": - case "Newtonsoft.Json.Linq.JObject": - case "Newtonsoft.Json.Linq.JArray": - left = objExp == null ? null : getExp(objExp); - switch (callExp.Method.Name) - { - case "get_Item": return $"{left}->{getExp(callExp.Arguments[argIndex])}"; - case "Any": return $"(jsonb_array_length(coalesce({left},'[]')) > 0)"; - case "Contains": - var json = getExp(callExp.Arguments[argIndex]); - if (objType == typeof(JArray)) - return $"(coalesce({left},'[]') ? ({json})::text)"; - if (json.StartsWith("'") && json.EndsWith("'")) - return - $"(coalesce({left},'{{}}') @> {_common.FormatSql("{0}", JToken.Parse(json.Trim('\'')))})"; - return $"(coalesce({left},'{{}}') @> ({json})::jsonb)"; - case "ContainsKey": - return $"(coalesce({left},'{{}}') ? {getExp(callExp.Arguments[argIndex])})"; - case "Concat": - var right2 = getExp(callExp.Arguments[argIndex]); - return $"(coalesce({left},'{{}}') || {right2})"; - case "LongCount": - case "Count": return $"jsonb_array_length(coalesce({left},'[]'))"; - case "Parse": - var json2 = getExp(callExp.Arguments[argIndex]); - if (json2.StartsWith("'") && json2.EndsWith("'")) - return _common.FormatSql("{0}", JToken.Parse(json2.Trim('\''))); - return $"({json2})::jsonb"; - } - - break; - } - - if (objType == typeof(Dictionary)) - { - left = objExp == null ? null : getExp(objExp); - switch (callExp.Method.Name) - { - case "get_Item": return $"{left}->{getExp(callExp.Arguments[argIndex])}"; - case "Contains": - var right = getExp(callExp.Arguments[argIndex]); - return $"({left} @> ({right}))"; - case "ContainsKey": return $"({left} ? {getExp(callExp.Arguments[argIndex])})"; - case "Concat": return $"({left} || {getExp(callExp.Arguments[argIndex])})"; - case "GetLength": - case "GetLongLength": - case "Count": - return $"case when {left} is null then 0 else array_length(akeys({left}),1) end"; - case "Keys": return $"akeys({left})"; - case "Values": return $"avals({left})"; - } - } - + if (argIndex >= callExp.Arguments.Count) break; + tsc.SetMapColumnTmp(null); + var args1 = getExp(callExp.Arguments[argIndex]); + var oldMapType = tsc.SetMapTypeReturnOld(tsc.mapTypeTmp); + var oldDbParams = objExp?.NodeType == ExpressionType.MemberAccess ? tsc.SetDbParamsReturnOld(null) : null; //#900 UseGenerateCommandParameterWithLambda(true) 子查询 bug、以及 #1173 参数化 bug + tsc.isNotSetMapColumnTmp = true; + var left = objExp == null ? null : getExp(objExp); + tsc.isNotSetMapColumnTmp = false; + tsc.SetMapColumnTmp(null).SetMapTypeReturnOld(oldMapType); + if (oldDbParams != null) tsc.SetDbParamsReturnOld(oldDbParams); switch (callExp.Method.Name) { - case "Any": - left = objExp == null ? null : getExp(objExp); - if (left.StartsWith("(") || left.EndsWith(")")) - left = $"array[{left.TrimStart('(').TrimEnd(')')}]"; - return $"(case when {left} is null then 0 else array_length({left},1) end > 0)"; case "Contains": - tsc.SetMapColumnTmp(null); - var args1 = getExp(callExp.Arguments[argIndex]); - var oldMapType = tsc.SetMapTypeReturnOld(tsc.mapTypeTmp); - var oldDbParams = objExp?.NodeType == ExpressionType.MemberAccess - ? tsc.SetDbParamsReturnOld(null) - : null; //#900 UseGenerateCommandParameterWithLambda(true) 子查询 bug、以及 #1173 参数化 bug - tsc.isNotSetMapColumnTmp = true; - left = objExp == null ? null : getExp(objExp); - tsc.isNotSetMapColumnTmp = false; - tsc.SetMapColumnTmp(null).SetMapTypeReturnOld(oldMapType); - if (oldDbParams != null) tsc.SetDbParamsReturnOld(oldDbParams); - //判断 in 或 array @> array - if (left.StartsWith("array[") || left.EndsWith("]")) - return $"({args1}) in ({left.Substring(6, left.Length - 7)})"; - if (left.StartsWith("(") || - left.EndsWith(")")) //在各大 Provider AdoProvider 中已约定,500元素分割, 3空格\r\n4空格 - return - $"(({args1}) in {left.Replace(", \r\n \r\n", $") \r\n OR ({args1}) in (")})"; - if (args1.StartsWith("(") || args1.EndsWith(")")) - args1 = $"array[{args1.TrimStart('(').TrimEnd(')')}]"; - args1 = $"array[{args1}]"; - if (objExp != null) - { - var dbinfo = _common._orm.CodeFirst.GetDbInfo(objExp.Type); - if (dbinfo != null) args1 = $"{args1}::{dbinfo.dbtype}"; - } - - return $"({left} @> {args1})"; - case "Concat": - left = objExp == null ? null : getExp(objExp); - if (left.StartsWith("(") || left.EndsWith(")")) - left = $"array[{left.TrimStart('(').TrimEnd(')')}]"; - var right2 = getExp(callExp.Arguments[argIndex]); - if (right2.StartsWith("(") || right2.EndsWith(")")) - right2 = $"array[{right2.TrimStart('(').TrimEnd(')')}]"; - return $"({left} || {right2})"; - case "GetLength": - case "GetLongLength": - case "Length": - case "Count": - left = objExp == null ? null : getExp(objExp); - if (left.StartsWith("(") || left.EndsWith(")")) - left = $"array[{left.TrimStart('(').TrimEnd(')')}]"; - return $"case when {left} is null then 0 else array_length({left},1) end"; + //判断 in //在各大 Provider AdoProvider 中已约定,500元素分割, 3空格\r\n4空格 + return $"(({args1}) in {left.Replace(", \r\n \r\n", $") \r\n OR ({args1}) in (")})"; } } - - break; - case ExpressionType.MemberAccess: - var memExp = exp as MemberExpression; - var memParentExp = memExp.Expression?.Type; - if (memParentExp?.FullName == "System.Byte[]") return null; - if (memParentExp != null) - { - if (memParentExp.IsArray == true) - { - var left = getExp(memExp.Expression); - if (left.StartsWith("(") || left.EndsWith(")")) - left = $"array[{left.TrimStart('(').TrimEnd(')')}]"; - switch (memExp.Member.Name) - { - case "Length": - case "Count": return $"case when {left} is null then 0 else array_length({left},1) end"; - } - } - - switch (memParentExp.FullName) - { - case "Newtonsoft.Json.Linq.JToken": - case "Newtonsoft.Json.Linq.JObject": - case "Newtonsoft.Json.Linq.JArray": - var left = getExp(memExp.Expression); - switch (memExp.Member.Name) - { - case "Count": return $"jsonb_array_length(coalesce({left},'[]'))"; - } - - break; - } - - if (memParentExp == typeof(Dictionary)) - { - var left = getExp(memExp.Expression); - switch (memExp.Member.Name) - { - case "Count": - return $"case when {left} is null then 0 else array_length(akeys({left}),1) end"; - case "Keys": return $"akeys({left})"; - case "Values": return $"avals({left})"; - } - } - } - break; case ExpressionType.NewArrayInit: var arrExp = exp as NewArrayExpression; var arrSb = new StringBuilder(); - arrSb.Append("array["); + arrSb.Append("("); for (var a = 0; a < arrExp.Expressions.Count; a++) { if (a > 0) arrSb.Append(","); + if (a % 500 == 499) arrSb.Append(" \r\n \r\n"); //500元素分割, 3空格\r\n4空格 arrSb.Append(getExp(arrExp.Expressions[a])); } - if (arrSb.Length == 1) arrSb.Append("NULL"); - return arrSb.Append("]").ToString(); + return arrSb.Append(")").ToString(); case ExpressionType.ListInit: var listExp = exp as ListInitExpression; var listSb = new StringBuilder(); @@ -309,7 +164,6 @@ namespace FreeSql.QuestDb if (a > 0) listSb.Append(","); listSb.Append(getExp(listExp.Initializers[a].Arguments.FirstOrDefault())); } - if (listSb.Length == 1) listSb.Append("NULL"); return listSb.Append(")").ToString(); case ExpressionType.New: @@ -320,7 +174,6 @@ namespace FreeSql.QuestDb if (typeof(IEnumerable).IsAssignableFrom(newExp.Arguments[0].Type) == false) return "(NULL)"; return getExp(newExp.Arguments[0]); } - return null; } @@ -342,7 +195,7 @@ namespace FreeSql.QuestDb var left = ExpressionLambdaToSql(exp.Expression, tsc); switch (exp.Member.Name) { - case "Length": return $"char_length({left})"; + case "Length": return $"length({left})"; } return null; @@ -356,33 +209,29 @@ namespace FreeSql.QuestDb { case "Now": return _common.Now; case "UtcNow": return _common.NowUtc; - case "Today": return $"date_trunc('day',{_common.NowUtc})"; - case "MinValue": return "'0001/1/1 0:00:00'::timestamp"; - case "MaxValue": return "'9999/12/31 23:59:59'::timestamp"; + case "Today": return $"date_trunc('day',{_common.Now})"; + case "MinValue": return "to_timestamp('0001-01-01', 'yyyy-MM-dd')"; + case "MaxValue": return "to_timestamp('9999-12-31 23:59:59.999999', 'yyyy-MM-dd HH:mm:ss.SSSUUU')"; } - return null; } var left = ExpressionLambdaToSql(exp.Expression, tsc); switch (exp.Member.Name) { - case "Date": return $"({left})::date"; - case "TimeOfDay": return $"(extract(epoch from ({left})::time)*1000000)"; - case "DayOfWeek": return $"extract(dow from ({left})::timestamp)"; - case "Day": return $"extract(day from ({left})::timestamp)"; - case "DayOfYear": return $"extract(doy from ({left})::timestamp)"; - case "Month": return $"extract(month from ({left})::timestamp)"; - case "Year": return $"extract(year from ({left})::timestamp)"; - case "Hour": return $"extract(hour from ({left})::timestamp)"; - case "Minute": return $"extract(minute from ({left})::timestamp)"; - case "Second": return $"extract(second from ({left})::timestamp)"; - case "Millisecond": - return - $"(extract(milliseconds from ({left})::timestamp)-extract(second from ({left})::timestamp)*1000)"; - case "Ticks": return $"(extract(epoch from ({left})::timestamp)*10000000+621355968000000000)"; + case "Date": return $"date_trunc('day',{left})"; + case "TimeOfDay": return $"(hour({left})*3600+minute({left})*60+second({left})"; + case "DayOfWeek": return $"(day_of_week_sunday_first({left})-1)"; + case "Day": return $"day({left})"; + case "DayOfYear": return $"datediff('d',date_trunc('year',{left}),{left})"; + case "Month": return $"month({left})"; + case "Year": return $"year({left})"; + case "Hour": return $"hour({left})"; + case "Minute": return $"minute({left})"; + case "Second": return $"second({left})"; + case "Millisecond":return $"millis({left})"; + case "Ticks": return $"(extract(epoch from {left})*10000000+millis({left})*10000+micros({left})*10+621355968000000000)"; } - return null; } @@ -403,17 +252,17 @@ namespace FreeSql.QuestDb var left = ExpressionLambdaToSql(exp.Expression, tsc); switch (exp.Member.Name) { - case "Days": return $"floor(({left})/{(long)1000000 * 60 * 60 * 24})"; - case "Hours": return $"floor(({left})/{(long)1000000 * 60 * 60}%24)"; - case "Milliseconds": return $"(floor(({left})/1000)::int8%1000)"; - case "Minutes": return $"(floor(({left})/{(long)1000000 * 60})::int8%60)"; - case "Seconds": return $"(floor(({left})/1000000)::int8%60)"; - case "Ticks": return $"(({left})*10)"; - case "TotalDays": return $"(({left})/{(long)1000000 * 60 * 60 * 24})"; - case "TotalHours": return $"(({left})/{(long)1000000 * 60 * 60})"; - case "TotalMilliseconds": return $"(({left})/1000)"; - case "TotalMinutes": return $"(({left})/{(long)1000000 * 60})"; - case "TotalSeconds": return $"(({left})/1000000)"; + case "Days": return $"floor(({left})/{60 * 60 * 24})"; + case "Hours": return $"floor(({left})/{60 * 60}%24)"; + case "Milliseconds": return $"0"; + case "Minutes": return $"(floor(({left})/60)%60)"; + case "Seconds": return $"(({left})%60)"; + case "Ticks": return $"(({left})*{(long)1000000 * 10})"; + case "TotalDays": return $"(({left})/{60 * 60 * 24})"; + case "TotalHours": return $"(({left})/{60 * 60})"; + case "TotalMilliseconds": return $"(({left})*1000)"; + case "TotalMinutes": return $"(({left})/60)"; + case "TotalSeconds": return $"({left})"; } return null; @@ -431,7 +280,7 @@ namespace FreeSql.QuestDb return $"({arg1} is null or {arg1} = '')"; case "IsNullOrWhiteSpace": var arg2 = getExp(exp.Arguments[0]); - return $"({arg2} is null or {arg2} = '' or ltrim({arg2}) = '')"; + return $"({arg2} is null or {arg2} = '' or trim({arg2}) = '')"; case "Concat": if (exp.Arguments.Count == 1 && exp.Arguments[0].NodeType == ExpressionType.NewArrayInit && exp.Arguments[0] is NewArrayExpression concatNewArrExp) return _common.StringConcat(concatNewArrExp.Expressions.Select(a => getExp(a)).ToArray(), null); @@ -452,7 +301,7 @@ namespace FreeSql.QuestDb a.Type.NullableTypeOrThis(); if (atype == typeof(string)) return $"'||{_common.IsNull(ExpressionLambdaToSql(a, tsc), "''")}||'"; - return $"'||{_common.IsNull($"({ExpressionLambdaToSql(a, tsc)})::text", "''")}||'"; + return $"'||{_common.IsNull($"cast({ExpressionLambdaToSql(a, tsc)} as string)", "''")}||'"; }).ToArray(); return string.Format(ExpressionLambdaToSql(exp.Arguments[0], tsc), expArgs); case "Join": @@ -486,34 +335,30 @@ namespace FreeSql.QuestDb { if (exp.Method.Name == "StartsWith") return $"strpos({args0Value}, {left}) = 1"; if (exp.Method.Name == "EndsWith") - return $"strpos({args0Value}, {left}) = char_length({args0Value})"; + return $"strpos({args0Value}, {left}) = length({args0Value})"; return $"strpos({args0Value}, {left}) > 0"; } var likeOpt = "LIKE"; if (exp.Arguments.Count > 1) { - if (exp.Arguments[1].Type == typeof(bool) || - exp.Arguments[1].Type == typeof(StringComparison)) likeOpt = "ILIKE"; + if (exp.Arguments[1].Type == typeof(bool) || exp.Arguments[1].Type == typeof(StringComparison)) likeOpt = "ILIKE"; } if (exp.Method.Name == "StartsWith") - return - $"({left}) {likeOpt} {(args0Value.EndsWith("'") ? args0Value.Insert(args0Value.Length - 1, "%") : $"(({args0Value})::text || '%')")}"; + return $"({left}) {likeOpt} {(args0Value.EndsWith("'") ? args0Value.Insert(args0Value.Length - 1, "%") : $"(cast({args0Value} as string) || '%')")}"; if (exp.Method.Name == "EndsWith") - return - $"({left}) {likeOpt} {(args0Value.StartsWith("'") ? args0Value.Insert(1, "%") : $"('%' || ({args0Value})::text)")}"; - if (args0Value.StartsWith("'") && args0Value.EndsWith("'")) - return $"({left}) {likeOpt} {args0Value.Insert(1, "%").Insert(args0Value.Length, "%")}"; - return $"({left}) {likeOpt} ('%' || ({args0Value})::text || '%')"; + return $"({left}) {likeOpt} {(args0Value.StartsWith("'") ? args0Value.Insert(1, "%") : $"('%' || cast({args0Value} as string))")}"; + if (args0Value.StartsWith("'") && args0Value.EndsWith("'")) return $"({left}) {likeOpt} {args0Value.Insert(1, "%").Insert(args0Value.Length, "%")}"; + return $"({left}) {likeOpt} ('%' || cast({args0Value} as string) || '%')"; case "ToLower": return $"lower({left})"; case "ToUpper": return $"upper({left})"; case "Substring": var substrArgs1 = getExp(exp.Arguments[0]); if (long.TryParse(substrArgs1, out var testtrylng1)) substrArgs1 = (testtrylng1 + 1).ToString(); else substrArgs1 += "+1"; - if (exp.Arguments.Count == 1) return $"substr({left}, {substrArgs1})"; - return $"substr({left}, {substrArgs1}, {getExp(exp.Arguments[1])})"; + if (exp.Arguments.Count == 1) return $"substring({left}, {substrArgs1})"; + return $"substring({left}, {substrArgs1}, {getExp(exp.Arguments[1])})"; case "IndexOf": return $"(strpos({left}, {getExp(exp.Arguments[0])})-1)"; case "PadLeft": if (exp.Arguments.Count == 1) return $"lpad({left}, {getExp(exp.Arguments[0])})"; @@ -558,10 +403,8 @@ namespace FreeSql.QuestDb left = $"rtrim({left}, {_common.FormatSql("{0}", trimArg1)}{trimArg2})"; return left; case "Replace": return $"replace({left}, {getExp(exp.Arguments[0])}, {getExp(exp.Arguments[1])})"; - case "CompareTo": - return - $"case when {left} = {getExp(exp.Arguments[0])} then 0 when {left} > {getExp(exp.Arguments[0])} then 1 else -1 end"; - case "Equals": return $"({left} = ({getExp(exp.Arguments[0])})::text)"; + case "CompareTo": return $"case when {left} = {getExp(exp.Arguments[0])} then 0 when {left} > {getExp(exp.Arguments[0])} then 1 else -1 end"; + case "Equals": return $"({left} = cast({getExp(exp.Arguments[0])} as string))"; } } @@ -606,26 +449,14 @@ namespace FreeSql.QuestDb { switch (exp.Method.Name) { - case "Compare": - return - $"extract(epoch from ({getExp(exp.Arguments[0])})::timestamp-({getExp(exp.Arguments[1])})::timestamp)"; - case "DaysInMonth": - return - $"extract(day from ({getExp(exp.Arguments[0])} || '-' || {getExp(exp.Arguments[1])} || '-01')::timestamp+'1 month'::interval-'1 day'::interval)"; - case "Equals": - return $"(({getExp(exp.Arguments[0])})::timestamp = ({getExp(exp.Arguments[1])})::timestamp)"; - - case "IsLeapYear": - var isLeapYearArgs1 = getExp(exp.Arguments[0]); - return - $"(({isLeapYearArgs1})::int8%4=0 AND ({isLeapYearArgs1})::int8%100<>0 OR ({isLeapYearArgs1})::int8%400=0)"; - - case "Parse": - return ExpressionConstDateTime(exp.Arguments[0]) ?? $"({getExp(exp.Arguments[0])})::timestamp"; + case "Compare": return $"datediff('s',{getExp(exp.Arguments[0])},{getExp(exp.Arguments[1])})"; + case "DaysInMonth": return $"days_in_month(to_date({getExp(exp.Arguments[0])} || '-' || {getExp(exp.Arguments[1])},'yyyy-MM'))"; + case "Equals": return $"({getExp(exp.Arguments[0])} = {getExp(exp.Arguments[1])})"; + case "IsLeapYear": return $"is_leap_year(to_date({getExp(exp.Arguments[0])},'yyyy'))"; + case "Parse": return ExpressionConstDateTime(exp.Arguments[0]) ?? $"cast({getExp(exp.Arguments[0])} as timestamp)"; case "ParseExact": case "TryParse": - case "TryParseExact": - return ExpressionConstDateTime(exp.Arguments[0]) ?? $"({getExp(exp.Arguments[0])})::timestamp"; + case "TryParseExact": return ExpressionConstDateTime(exp.Arguments[0]) ?? $"cast({getExp(exp.Arguments[0])} as timestamp)"; } } else @@ -634,111 +465,70 @@ namespace FreeSql.QuestDb var args1 = exp.Arguments.Count == 0 ? null : getExp(exp.Arguments[0]); switch (exp.Method.Name) { - case "Add": return $"(({left})::timestamp+((({args1})/1000)||' milliseconds')::interval)"; - case "AddDays": return $"(({left})::timestamp+(({args1})||' day')::interval)"; - case "AddHours": return $"(({left})::timestamp+(({args1})||' hour')::interval)"; - case "AddMilliseconds": return $"(({left})::timestamp+(({args1})||' milliseconds')::interval)"; - case "AddMinutes": return $"(({left})::timestamp+(({args1})||' minute')::interval)"; - case "AddMonths": return $"(({left})::timestamp+(({args1})||' month')::interval)"; - case "AddSeconds": return $"(({left})::timestamp+(({args1})||' second')::interval)"; - case "AddTicks": return $"(({left})::timestamp+(({args1})/10||' microseconds')::interval)"; - case "AddYears": return $"(({left})::timestamp+(({args1})||' year')::interval)"; + case "Add": return $"dateadd('s',{args1},{left})"; + case "AddDays": return $"dateadd('d',{args1},{left})"; + case "AddHours": return $"dateadd('h',{args1},{left})"; + case "AddMilliseconds": return $"dateadd('s',{args1}/1000,{left})"; + case "AddMinutes": return $"dateadd('m',{args1},{left})"; + case "AddMonths": return $"dateadd('M',{args1},{left})"; + case "AddSeconds": return $"dateadd('s',{args1},{left})"; + case "AddTicks": return $"dateadd('s',{args1}/10000000,{left})"; + case "AddYears": return $"dateadd('y',{args1},{left})"; case "Subtract": - switch ((exp.Arguments[0].Type.IsNullableType() - ? exp.Arguments[0].Type.GetGenericArguments().FirstOrDefault() - : exp.Arguments[0].Type).FullName) + switch ((exp.Arguments[0].Type.IsNullableType() ? exp.Arguments[0].Type.GetGenericArguments().FirstOrDefault() : exp.Arguments[0].Type).FullName) { - case "System.DateTime": - return $"(extract(epoch from ({left})::timestamp-({args1})::timestamp)*1000000)"; - case "System.TimeSpan": - return $"(({left})::timestamp-((({args1})/1000)||' milliseconds')::interval)"; + case "System.DateTime": return $"datediff('s',{args1},{left})"; + case "System.TimeSpan": return $"dateadd('s',({args1})*-1,{left})"; } - break; - case "Equals": return $"({left} = ({args1})::timestamp)"; - case "CompareTo": return $"extract(epoch from ({left})::timestamp-({args1})::timestamp)"; + case "Equals": return $"({left} = cast({args1} as timestamp))"; + case "CompareTo": return $"datediff('s',{args1},{left})"; case "ToString": - if (left.EndsWith("::timestamp") == false) left = $"({left})::timestamp"; - if (exp.Arguments.Count == 0) return $"to_char({left},'YYYY-MM-DD HH24:MI:SS.US')"; - switch (args1) + if (left.EndsWith(" as timestamp)") == false) left = $"cast({left} as datetime)"; + if (exp.Arguments.Count == 0) return $"convert(varchar, {left}, 121)"; + switch (args1.TrimStart('N')) { - case "'yyyy-MM-dd HH:mm:ss'": return $"to_char({left},'YYYY-MM-DD HH24:MI:SS')"; - case "'yyyy-MM-dd HH:mm'": return $"to_char({left},'YYYY-MM-DD HH24:MI')"; - case "'yyyy-MM-dd HH'": return $"to_char({left},'YYYY-MM-DD HH24')"; - case "'yyyy-MM-dd'": return $"to_char({left},'YYYY-MM-DD')"; - case "'yyyy-MM'": return $"to_char({left},'YYYY-MM')"; - case "'yyyyMMddHHmmss'": return $"to_char({left},'YYYYMMDDHH24MISS')"; - case "'yyyyMMddHHmm'": return $"to_char({left},'YYYYMMDDHH24MI')"; - case "'yyyyMMddHH'": return $"to_char({left},'YYYYMMDDHH24')"; - case "'yyyyMMdd'": return $"to_char({left},'YYYYMMDD')"; - case "'yyyyMM'": return $"to_char({left},'YYYYMM')"; - case "'yyyy'": return $"to_char({left},'YYYY')"; - case "'HH:mm:ss'": return $"to_char({left},'HH24:MI:SS')"; + case "'yyyy-MM-dd HH:mm:ss'": return $"convert(char(19), {left}, 120)"; + case "'yyyy-MM-dd HH:mm'": return $"substring(convert(char(19), {left}, 120), 1, 16)"; + case "'yyyy-MM-dd HH'": return $"substring(convert(char(19), {left}, 120), 1, 13)"; + case "'yyyy-MM-dd'": return $"convert(char(10), {left}, 23)"; + case "'yyyy-MM'": return $"substring(convert(char(10), {left}, 23), 1, 7)"; + case "'yyyyMMdd'": return $"convert(char(8), {left}, 112)"; + case "'yyyyMM'": return $"substring(convert(char(8), {left}, 112), 1, 6)"; + case "'yyyy'": return $"substring(convert(char(8), {left}, 112), 1, 4)"; + case "'HH:mm:ss'": return $"convert(char(8), {left}, 24)"; } - - args1 = Regex.Replace(args1, "(yyyy|yy|MM|dd|HH|hh|mm|ss|tt)", m => + var isMatched = false; + var nchar = args1.StartsWith("N'") ? "N" : ""; + args1 = Regex.Replace(args1, "(yyyy|yy|MM|M|dd|d|HH|H|hh|h|mm|m|ss|s|tt|t)", m => { + isMatched = true; switch (m.Groups[1].Value) { - case "yyyy": return $"YYYY"; - case "yy": return $"YY"; - case "MM": return $"%_a1"; - case "dd": return $"%_a2"; - case "HH": return $"%_a3"; - case "hh": return $"%_a4"; - case "mm": return $"%_a5"; - case "ss": return $"SS"; - case "tt": return $"%_a6"; + case "yyyy": return $"' + substring(convert(char(8), {left}, 112), 1, 4) + {nchar}'"; + case "yy": return $"' + substring(convert(char(6), {left}, 12), 1, 2) + {nchar}'"; + case "MM": return $"' + substring(convert(char(6), {left}, 12), 3, 2) + {nchar}'"; + case "M": return $"' + case when substring(convert(char(6), {left}, 12), 3, 1) = '0' then substring(convert(char(6), {left}, 12), 4, 1) else substring(convert(char(6), {left}, 12), 3, 2) end + {nchar}'"; + case "dd": return $"' + substring(convert(char(6), {left}, 12), 5, 2) + {nchar}'"; + case "d": return $"' + case when substring(convert(char(6), {left}, 12), 5, 1) = '0' then substring(convert(char(6), {left}, 12), 6, 1) else substring(convert(char(6), {left}, 12), 5, 2) end + {nchar}'"; + case "HH": return $"' + substring(convert(char(8), {left}, 24), 1, 2) + {nchar}'"; + case "H": return $"' + case when substring(convert(char(8), {left}, 24), 1, 1) = '0' then substring(convert(char(8), {left}, 24), 2, 1) else substring(convert(char(8), {left}, 24), 1, 2) end + {nchar}'"; + case "hh": + return $"' + case cast(case when substring(convert(char(8), {left}, 24), 1, 1) = '0' then substring(convert(char(8), {left}, 24), 2, 1) else substring(convert(char(8), {left}, 24), 1, 2) end as int) % 12" + + $"when 0 then '12' when 1 then '01' when 2 then '02' when 3 then '03' when 4 then '04' when 5 then '05' when 6 then '06' when 7 then '07' when 8 then '08' when 9 then '09' when 10 then '10' when 11 then '11' end + {nchar}'"; + case "h": + return $"' + case cast(case when substring(convert(char(8), {left}, 24), 1, 1) = '0' then substring(convert(char(8), {left}, 24), 2, 1) else substring(convert(char(8), {left}, 24), 1, 2) end as int) % 12" + + $"when 0 then '12' when 1 then '1' when 2 then '2' when 3 then '3' when 4 then '4' when 5 then '5' when 6 then '6' when 7 then '7' when 8 then '8' when 9 then '9' when 10 then '10' when 11 then '11' end + {nchar}'"; + case "mm": return $"' + substring(convert(char(8), {left}, 24), 4, 2) + {nchar}'"; + case "m": return $"' + case when substring(convert(char(8), {left}, 24), 4, 1) = '0' then substring(convert(char(8), {left}, 24), 5, 1) else substring(convert(char(8), {left}, 24), 4, 2) end + {nchar}'"; + case "ss": return $"' + substring(convert(char(8), {left}, 24), 7, 2) + {nchar}'"; + case "s": return $"' + case when substring(convert(char(8), {left}, 24), 7, 1) = '0' then substring(convert(char(8), {left}, 24), 8, 1) else substring(convert(char(8), {left}, 24), 7, 2) end + {nchar}'"; + case "tt": return $"' + case when cast(case when substring(convert(char(8), {left}, 24), 1, 1) = '0' then substring(convert(char(8), {left}, 24), 2, 1) else substring(convert(char(8), {left}, 24), 1, 2) end as int) >= 12 then 'PM' else 'AM' end + {nchar}'"; + case "t": return $"' + case when cast(case when substring(convert(char(8), {left}, 24), 1, 1) = '0' then substring(convert(char(8), {left}, 24), 2, 1) else substring(convert(char(8), {left}, 24), 1, 2) end as int) >= 12 then 'P' else 'A' end + {nchar}'"; } - return m.Groups[0].Value; }); - var argsFinds = new[] { "YYYY", "YY", "%_a1", "%_a2", "%_a3", "%_a4", "%_a5", "SS", "%_a6" }; - var argsSpts = Regex.Split(args1, "(M|d|H|h|m|s|t)"); - for (var a = 0; a < argsSpts.Length; a++) - { - switch (argsSpts[a]) - { - case "M": - argsSpts[a] = $"ltrim(to_char({left},'MM'),'0')"; - break; - case "d": - argsSpts[a] = - $"case when substr(to_char({left},'DD'),1,1) = '0' then substr(to_char({left},'DD'),2,1) else to_char({left},'DD') end"; - break; - case "H": - argsSpts[a] = - $"case when substr(to_char({left},'HH24'),1,1) = '0' then substr(to_char({left},'HH24'),2,1) else to_char({left},'HH24') end"; - break; - case "h": - argsSpts[a] = - $"case when substr(to_char({left},'HH12'),1,1) = '0' then substr(to_char({left},'HH12'),2,1) else to_char({left},'HH12') end"; - break; - case "m": - argsSpts[a] = - $"case when substr(to_char({left},'MI'),1,1) = '0' then substr(to_char({left},'MI'),2,1) else to_char({left},'MI') end"; - break; - case "s": - argsSpts[a] = - $"case when substr(to_char({left},'SS'),1,1) = '0' then substr(to_char({left},'SS'),2,1) else to_char({left},'SS') end"; - break; - case "t": - argsSpts[a] = $"rtrim(to_char({left},'AM'),'M')"; - break; - default: - var argsSptsA = argsSpts[a]; - if (argsSptsA.StartsWith("'")) argsSptsA = argsSptsA.Substring(1); - if (argsSptsA.EndsWith("'")) argsSptsA = argsSptsA.Remove(argsSptsA.Length - 1); - argsSpts[a] = argsFinds.Any(m => argsSptsA.Contains(m)) - ? $"to_char({left},'{argsSptsA}')" - : $"'{argsSptsA}'"; - break; - } - } - - if (argsSpts.Length > 0) args1 = $"({string.Join(" || ", argsSpts.Where(a => a != "''"))})"; - return args1.Replace("%_a1", "MM").Replace("%_a2", "DD").Replace("%_a3", "HH24") - .Replace("%_a4", "HH12").Replace("%_a5", "MI").Replace("%_a6", "AM"); + return isMatched == false ? args1 : $"({args1})"; } } @@ -754,16 +544,16 @@ namespace FreeSql.QuestDb { case "Compare": return $"({getExp(exp.Arguments[0])}-({getExp(exp.Arguments[1])}))"; case "Equals": return $"({getExp(exp.Arguments[0])} = {getExp(exp.Arguments[1])})"; - case "FromDays": return $"(({getExp(exp.Arguments[0])})*{(long)1000000 * 60 * 60 * 24})"; - case "FromHours": return $"(({getExp(exp.Arguments[0])})*{(long)1000000 * 60 * 60})"; - case "FromMilliseconds": return $"(({getExp(exp.Arguments[0])})*1000)"; - case "FromMinutes": return $"(({getExp(exp.Arguments[0])})*{(long)1000000 * 60})"; - case "FromSeconds": return $"(({getExp(exp.Arguments[0])})*1000000)"; - case "FromTicks": return $"(({getExp(exp.Arguments[0])})/10)"; - case "Parse": return $"({getExp(exp.Arguments[0])})::int8"; + case "FromDays": return $"(({getExp(exp.Arguments[0])})*{60 * 60 * 24})"; + case "FromHours": return $"(({getExp(exp.Arguments[0])})*{60 * 60})"; + case "FromMilliseconds": return $"(({getExp(exp.Arguments[0])})/1000)"; + case "FromMinutes": return $"(({getExp(exp.Arguments[0])})*60)"; + case "FromSeconds": return $"({getExp(exp.Arguments[0])})"; + case "FromTicks": return $"(({getExp(exp.Arguments[0])})/10000000)"; + case "Parse": return $"cast({getExp(exp.Arguments[0])} as long)"; case "ParseExact": case "TryParse": - case "TryParseExact": return $"({getExp(exp.Arguments[0])})::int8"; + case "TryParseExact": return $"cast({getExp(exp.Arguments[0])} as long)"; } } else @@ -776,7 +566,7 @@ namespace FreeSql.QuestDb case "Subtract": return $"({left}-({args1}))"; case "Equals": return $"({left} = {args1})"; case "CompareTo": return $"({left}-({args1}))"; - case "ToString": return $"({left})::varchar"; + case "ToString": return $"cast({left} as string)"; } } @@ -790,22 +580,22 @@ namespace FreeSql.QuestDb { switch (exp.Method.Name) { - case "ToBoolean": return $"(({getExp(exp.Arguments[0])})::varchar not in ('0','false','f','no'))"; - case "ToByte": return $"({getExp(exp.Arguments[0])})::int2"; - case "ToChar": return $"substr(({getExp(exp.Arguments[0])})::char, 1, 1)"; + case "ToBoolean": return $"(cast({getExp(exp.Arguments[0])} as string) not in ('0','false','f','no'))"; + case "ToByte": return $"cast({getExp(exp.Arguments[0])} as byte)"; + case "ToChar": return $"left(cast({getExp(exp.Arguments[0])} as string), 1)"; case "ToDateTime": - return ExpressionConstDateTime(exp.Arguments[0]) ?? $"({getExp(exp.Arguments[0])})::timestamp"; - case "ToDecimal": return $"({getExp(exp.Arguments[0])})::numeric"; - case "ToDouble": return $"({getExp(exp.Arguments[0])})::float8"; - case "ToInt16": return $"({getExp(exp.Arguments[0])})::int2"; - case "ToInt32": return $"({getExp(exp.Arguments[0])})::int4"; - case "ToInt64": return $"({getExp(exp.Arguments[0])})::int8"; - case "ToSByte": return $"({getExp(exp.Arguments[0])})::int2"; - case "ToSingle": return $"({getExp(exp.Arguments[0])})::float4"; - case "ToString": return $"({getExp(exp.Arguments[0])})::text"; - case "ToUInt16": return $"({getExp(exp.Arguments[0])})::int2"; - case "ToUInt32": return $"({getExp(exp.Arguments[0])})::int4"; - case "ToUInt64": return $"({getExp(exp.Arguments[0])})::int8"; + return ExpressionConstDateTime(exp.Arguments[0]) ?? $"cast({getExp(exp.Arguments[0])} as timestamp)"; + case "ToDecimal": return $"cast({getExp(exp.Arguments[0])} as double)"; + case "ToDouble": return $"cast({getExp(exp.Arguments[0])} as double)"; + case "ToInt16": return $"cast({getExp(exp.Arguments[0])} as short)"; + case "ToInt32": return $"cast({getExp(exp.Arguments[0])} as int)"; + case "ToInt64": return $"cast({getExp(exp.Arguments[0])} as long)"; + case "ToSByte": return $"cast({getExp(exp.Arguments[0])} as byte)"; + case "ToSingle": return $"cast({getExp(exp.Arguments[0])} as int)"; + case "ToString": return $"cast({getExp(exp.Arguments[0])} as string)"; + case "ToUInt16": return $"cast({getExp(exp.Arguments[0])} as short)"; + case "ToUInt32": return $"cast({getExp(exp.Arguments[0])} as int)"; + case "ToUInt64": return $"cast({getExp(exp.Arguments[0])} as long)"; } } diff --git a/Providers/FreeSql.Provider.QuestDb/QuestDbFunctions.cs b/Providers/FreeSql.Provider.QuestDb/QuestDbFunctions.cs new file mode 100644 index 00000000..c6466354 --- /dev/null +++ b/Providers/FreeSql.Provider.QuestDb/QuestDbFunctions.cs @@ -0,0 +1,252 @@ +using FreeSql; +using FreeSql.DataAnnotations; +using FreeSql.Internal.CommonProvider; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Numerics; +using System.Text; +using System.Threading; + +/// +/// QuestDB lambda 表达式树扩展解析 +/// https://questdb.io/docs/reference/function/aggregation/ +/// +[ExpressionCall] +public static class QuestFunc +{ + public static ThreadLocal expContext = new ThreadLocal(); + static ExpressionCallContext ec => expContext.Value; + static T call(string rt) + { + ec.Result = rt; + return default(T); + } + + public static decimal avg(object value) => call($"avg({ec.ParsedContent["value"]})"); + public static long count() => call($"count(*)"); + public static long count(object column_name) => call($"count({ec.ParsedContent["column_name"]})"); + public static long count_distinct(object column_name) => call($"count_distinct({ec.ParsedContent["column_name"]})"); + public static string first(object column_name) => call($"first({ec.ParsedContent["column_name"]})"); + public static string last(object column_name) => call($"last({ec.ParsedContent["column_name"]})"); + public static decimal haversine_dist_deg(decimal lat, decimal lon, DateTime ts) => call($"haversine_dist_deg({ec.ParsedContent["lat"]},{ec.ParsedContent["lon"]},{ec.ParsedContent["ts"]})"); + public static decimal ksum(object value) => call($"ksum({ec.ParsedContent["value"]})"); + public static T max(T value) => call($"max({ec.ParsedContent["value"]})"); + public static T min(T value) => call($"min({ec.ParsedContent["value"]})"); + public static decimal nsum(object value) => call($"nsum({ec.ParsedContent["value"]})"); + public static decimal stddev_samp(object value) => call($"stddev_samp({ec.ParsedContent["value"]})"); + public static decimal sum(object value) => call($"sum({ec.ParsedContent["value"]})"); + + public static bool isOrdered(object column) => call($"isOrdered({ec.ParsedContent["column"]})"); + public static T coalesce(object value1, object value2) => call($"coalesce({ec.ParsedContent["value1"]},{ec.ParsedContent["value2"]})"); + public static T coalesce(object value1, object value2, object value3) => call($"coalesce({ec.ParsedContent["value1"]},{ec.ParsedContent["value2"]},{ec.ParsedContent["value3"]})"); + public static T coalesce(object value1, object value2, object value3, object value4) => call($"coalesce({ec.ParsedContent["value1"]},{ec.ParsedContent["value2"]},{ec.ParsedContent["value3"]},{ec.ParsedContent["value4"]})"); + public static T coalesce(object value1, object value2, object value3, object value4, object value5) => call($"coalesce({ec.ParsedContent["value1"]},{ec.ParsedContent["value2"]},{ec.ParsedContent["value3"]},{ec.ParsedContent["value4"]},{ec.ParsedContent["value5"]})"); + public static T nullif(object value1, object value2) => call($"nullif({ec.ParsedContent["value1"]},{ec.ParsedContent["value2"]})"); + + public static DateTime date_trunc([RawValue] date_trunc_unit unit, object timestamp) => call($"date_trunc('{unit.ToString()}',{ec.ParsedContent["timestamp"]})"); + public enum date_trunc_unit + { + millennium, + decade, + century, + year, + quarter, + month, + week, + day, + hour, + minute, + second, + milliseconds, + microseconds, + } + public static DateTime dateadd([RawValue] date_period period, [RawValue] long n, object startDate) => call($"dateadd('{(char)(int)period}',{n},{ec.ParsedContent["startDate"]})"); + public static long datediff([RawValue] date_period period, object date1, object date2) => call($"datediff('{(char)period}',{ec.ParsedContent["date1"]},{ec.ParsedContent["date2"]})"); + public enum date_period + { + second = 's', + minute = 'm', + hour = 'h', + day = 'd', + week = 'w', + month = 'M', + year = 'y', + } + public static int day(object timestamp) => call($"day({ec.ParsedContent["timestamp"]})"); + public static int day_of_week(object timestamp) => call($"day_of_week({ec.ParsedContent["timestamp"]})"); + public static int day_of_week_sunday_first(object timestamp) => call($"day_of_week_sunday_first({ec.ParsedContent["timestamp"]})"); + public static long extract([RawValue] extract_unit unit, object timestamp) => call($"extract({unit.ToString()} from {ec.ParsedContent["timestamp"]})"); + public enum extract_unit + { + millennium, + epoch, + decade, + century, + year, + isoyear, + /// + /// day of year + /// + doy, + quarter, + month, + week, + /// + /// day of week + /// + dow, + isodow, + day, + hour, + minute, + second, + microseconds, + milliseconds, + } + public static int hour(object timestamp) => call($"hour({ec.ParsedContent["timestamp"]})"); + public static bool is_leap_year(object timestamp) => call($"is_leap_year({ec.ParsedContent["timestamp"]})"); + public static bool days_in_month(object timestamp) => call($"days_in_month({ec.ParsedContent["timestamp"]})"); + public static int micros(object timestamp) => call($"micros({ec.ParsedContent["timestamp"]})"); + public static int millis(object timestamp) => call($"millis({ec.ParsedContent["timestamp"]})"); + public static int minute(object timestamp) => call($"minute({ec.ParsedContent["timestamp"]})"); + public static int month(object timestamp) => call($"month({ec.ParsedContent["timestamp"]})"); + public static DateTime now() => call($"now()"); + public static DateTime pg_postmaster_start_time() => call($"pg_postmaster_start_time()"); + public static int second(object timestamp) => call($"second({ec.ParsedContent["timestamp"]})"); + /// + /// Use now() with WHERE clause filter. + /// + /// + public static DateTime systimestamp() => call($"systimestamp()"); + /// + /// Use now() with WHERE clause filter. + /// + /// + public static DateTime sysdate() => call($"sysdate()"); + + public static DateTime timestamp_ceil([RawValue] timestamp_ceil_unit unit, object timestamp) => call($"timestamp_ceil({(char)unit},{ec.ParsedContent["timestamp"]})"); + public static DateTime timestamp_floor([RawValue] timestamp_ceil_unit unit, object timestamp) => call($"timestamp_floor({(char)unit},{ec.ParsedContent["timestamp"]})"); + public enum timestamp_ceil_unit + { + milliseconds = 'T', + seconds = 's', + minutes = 'm', + hours = 'h', + days = 'd', + months = 'M', + year = 'y', + } + public static DateTime timestamp_shuffle(object timestamp_1, object timestamp_2) => call($"timestamp_shuffle({ec.ParsedContent["timestamp_1"]},{ec.ParsedContent["timestamp_2"]})"); + public static DateTime to_date(string str, string format) => call($"to_date({ec.ParsedContent["str"]},{ec.ParsedContent["format"]})"); + public static string to_str(DateTime value, string format) => call($"to_str({ec.ParsedContent["value"]},{ec.ParsedContent["format"]})"); + public static DateTime to_timestamp(string str, string format) => call($"to_timestamp({ec.ParsedContent["str"]},{ec.ParsedContent["format"]})"); + public static DateTime to_timezone(DateTime timestamp, string timezone) => call($"to_timezone({ec.ParsedContent["timestamp"]},{ec.ParsedContent["timezone"]})"); + public static DateTime to_utc(DateTime timestamp, string timezone) => call($"to_utc({ec.ParsedContent["timestamp"]},{ec.ParsedContent["timezone"]})"); + public static int week_of_year(object timestamp) => call($"week_of_year({ec.ParsedContent["timestamp"]})"); + public static int year(object timestamp) => call($"year({ec.ParsedContent["timestamp"]})"); + + public static T abs(T value) => call($"abs({ec.ParsedContent["value"]})"); + public static decimal log(object value) => call($"log({ec.ParsedContent["value"]})"); + public static decimal power(object _base, object exponent) => call($"power({ec.ParsedContent["_base"]},{ec.ParsedContent["exponent"]})"); + public static T round(T value, int scale) => call($"round({ec.ParsedContent["value"]},{ec.ParsedContent["scale"]})"); + public static T round_down(T value, int scale) => call($"round_down({ec.ParsedContent["value"]},{ec.ParsedContent["scale"]})"); + public static T round_half_even(T value, int scale) => call($"round_half_even({ec.ParsedContent["value"]},{ec.ParsedContent["scale"]})"); + public static T round_up(T value, int scale) => call($"round_up({ec.ParsedContent["value"]},{ec.ParsedContent["scale"]})"); + public static string size_pretty(long value) => call($"size_pretty({ec.ParsedContent["value"]})"); + public static decimal sqrt(object value) => call($"sqrt({ec.ParsedContent["value"]})"); + + public static bool rnd_boolean() => call($"rnd_boolean()"); + public static byte rnd_byte() => call($"rnd_byte()"); + public static byte rnd_byte(byte min, byte max) => call($"rnd_byte({ec.ParsedContent["min"]},{ec.ParsedContent["max"]})"); + public static short rnd_short() => call($"rnd_short()"); + public static short rnd_short(short min, short max) => call($"rnd_short({ec.ParsedContent["min"]},{ec.ParsedContent["max"]})"); + public static int rnd_int() => call($"rnd_int()"); + public static int rnd_int(int min, int max) => call($"rnd_int({ec.ParsedContent["min"]},{ec.ParsedContent["max"]})"); + public static int rnd_int(int min, int max, int nanRate) => call($"rnd_int({ec.ParsedContent["min"]},{ec.ParsedContent["max"]},{ec.ParsedContent["nanRate"]})"); + public static long rnd_long() => call($"rnd_long()"); + public static long rnd_long(long min, long max) => call($"rnd_long({ec.ParsedContent["min"]},{ec.ParsedContent["max"]})"); + public static long rnd_long(long min, long max, int nanRate) => call($"rnd_long({ec.ParsedContent["min"]},{ec.ParsedContent["max"]},{ec.ParsedContent["nanRate"]})"); + public static BigInteger rnd_long256() => call($"rnd_long256()"); + public static BigInteger rnd_long256(BigInteger min, BigInteger max) => call($"rnd_long256({ec.ParsedContent["min"]},{ec.ParsedContent["max"]})"); + public static BigInteger rnd_long256(BigInteger min, BigInteger max, int nanRate) => call($"rnd_long256({ec.ParsedContent["min"]},{ec.ParsedContent["max"]},{ec.ParsedContent["nanRate"]})"); + public static float rnd_float() => call($"rnd_float()"); + public static float rnd_float(int nanRate) => call($"rnd_float({ec.ParsedContent["nanRate"]})"); + public static double rnd_double() => call($"rnd_double()"); + public static double rnd_double(int nanRate) => call($"rnd_double({ec.ParsedContent["nanRate"]})"); + public static DateTime rnd_date(DateTime min, DateTime max) => call($"rnd_date({ec.ParsedContent["min"]},{ec.ParsedContent["max"]},0)"); + public static DateTime rnd_date(DateTime min, DateTime max, int nanRate) => call($"rnd_date({ec.ParsedContent["min"]},{ec.ParsedContent["max"]},{ec.ParsedContent["nanRate"]})"); + public static DateTime rnd_timestamp(DateTime min, DateTime max) => call($"rnd_timestamp({ec.ParsedContent["min"]},{ec.ParsedContent["max"]},0)"); + public static DateTime rnd_timestamp(DateTime min, DateTime max, int nanRate) => call($"rnd_timestamp({ec.ParsedContent["min"]},{ec.ParsedContent["max"]},{ec.ParsedContent["nanRate"]})"); + public static char rnd_char() => call($"rnd_char()"); + public static string rnd_symbol([RawValue] string[] symbolList) => call($"rnd_symbol({string.Join(",", symbolList.Select(a => ec.FormatSql(a)))})"); + public static string rnd_symbol(int list_size, int minLength, int maxLength, int nullRate) => call($"rnd_symbol({ec.ParsedContent["list_size"]},{ec.ParsedContent["minLength"]},{ec.ParsedContent["maxLength"]},{ec.ParsedContent["nullRate"]})"); + public static string rnd_str([RawValue] string[] stringList) => call($"rnd_str({string.Join(",", stringList.Select(a => ec.FormatSql(a)))})"); + public static string rnd_str(int list_size, int minLength, int maxLength, int nullRate) => call($"rnd_str({ec.ParsedContent["list_size"]},{ec.ParsedContent["minLength"]},{ec.ParsedContent["maxLength"]},{ec.ParsedContent["nullRate"]})"); + public static byte[] rnd_bin() => call($"rnd_bin()"); + public static byte[] rnd_bin(long minBytes, int maxBytes, int nullRate) => call($"rnd_bin({ec.ParsedContent["minBytes"]},{ec.ParsedContent["maxBytes"]},{ec.ParsedContent["nullRate"]})"); + public static Guid rnd_uuid4() => call($"rnd_uuid4()"); + + public static byte[] rnd_geohash(int bits) => call($"rnd_geohash({ec.ParsedContent["bits"]})"); + public static byte[] make_geohash(decimal lon, decimal lat, int bits) => call($"make_geohash({ec.ParsedContent["lon"]},{ec.ParsedContent["lat"]},{ec.ParsedContent["bits"]})"); + + public static string concat(object value1, object value2) => call($"coalesce({ec.ParsedContent["value1"]},{ec.ParsedContent["value2"]})"); + public static string concat(object value1, object value2, object value3) => call($"coalesce({ec.ParsedContent["value1"]},{ec.ParsedContent["value2"]},{ec.ParsedContent["value3"]})"); + public static string concat(object value1, object value2, object value3, object value4) => call($"coalesce({ec.ParsedContent["value1"]},{ec.ParsedContent["value2"]},{ec.ParsedContent["value3"]},{ec.ParsedContent["value4"]})"); + public static string concat(object value1, object value2, object value3, object value4, object value5) => call($"coalesce({ec.ParsedContent["value1"]},{ec.ParsedContent["value2"]},{ec.ParsedContent["value3"]},{ec.ParsedContent["value4"]},{ec.ParsedContent["value5"]})"); + public static string concat(object value1, object value2, object value3, object value4, object value5, object value6) => call($"coalesce({ec.ParsedContent["value1"]},{ec.ParsedContent["value2"]},{ec.ParsedContent["value3"]},{ec.ParsedContent["value4"]},{ec.ParsedContent["value5"]},{ec.ParsedContent["value6"]})"); + public static string concat(object value1, object value2, object value3, object value4, object value5, object value6, object value7) => call($"coalesce({ec.ParsedContent["value1"]},{ec.ParsedContent["value2"]},{ec.ParsedContent["value3"]},{ec.ParsedContent["value4"]},{ec.ParsedContent["value5"]},{ec.ParsedContent["value6"]},{ec.ParsedContent["value7"]})"); + public static string concat(object value1, object value2, object value3, object value4, object value5, object value6, object value7, object value8) => call($"coalesce({ec.ParsedContent["value1"]},{ec.ParsedContent["value2"]},{ec.ParsedContent["value3"]},{ec.ParsedContent["value4"]},{ec.ParsedContent["value5"]},{ec.ParsedContent["value6"]},{ec.ParsedContent["value7"]},{ec.ParsedContent["value8"]})"); + public static string concat(object value1, object value2, object value3, object value4, object value5, object value6, object value7, object value8, object value9) => call($"coalesce({ec.ParsedContent["value1"]},{ec.ParsedContent["value2"]},{ec.ParsedContent["value3"]},{ec.ParsedContent["value4"]},{ec.ParsedContent["value5"]},{ec.ParsedContent["value6"]},{ec.ParsedContent["value7"]},{ec.ParsedContent["value8"]},{ec.ParsedContent["value9"]})"); + public static string concat(object value1, object value2, object value3, object value4, object value5, object value6, object value7, object value8, object value9, object value10) => call($"coalesce({ec.ParsedContent["value1"]},{ec.ParsedContent["value2"]},{ec.ParsedContent["value3"]},{ec.ParsedContent["value4"]},{ec.ParsedContent["value5"]},{ec.ParsedContent["value6"]},{ec.ParsedContent["value7"]},{ec.ParsedContent["value8"]},{ec.ParsedContent["value9"]},{ec.ParsedContent["value10"]})"); + public static long length(object value) => call($"length({ec.ParsedContent["value"]})"); + public static string left(string str, int count) => call($"left({ec.ParsedContent["str"]},{ec.ParsedContent["count"]})"); + public static string right(string str, int count) => call($"right({ec.ParsedContent["str"]},{ec.ParsedContent["count"]})"); + public static int strpos(object str, string substr) => call($"strpos({ec.ParsedContent["str"]},{ec.ParsedContent["substr"]})"); + public static string substring(string str, int start, int length) => call($"substring({ec.ParsedContent["str"]},{ec.ParsedContent["start"]},{ec.ParsedContent["length"]})"); + public static string lower(string str) => call($"lower({ec.ParsedContent["str"]})"); + public static string upper(string str) => call($"upper({ec.ParsedContent["str"]})"); + + public static DateTime timestamp_sequence(DateTime startTimestamp, long step) => call($"timestamp_sequence({ec.ParsedContent["startTimestamp"]},{ec.ParsedContent["step"]})"); + public static string regexp_replace(string str1, string regex, string str2) => call($"regexp_replace({ec.ParsedContent["str1"]},{ec.ParsedContent["regex"]},{ec.ParsedContent["str2"]})"); + public static bool regex_match(string str, string regex) => call($"{ec.ParsedContent["str"]} ~ {ec.ParsedContent["regex"]}"); + public static bool regex_not_match(string str, string regex) => call($"{ec.ParsedContent["str"]} !~ {ec.ParsedContent["regex"]}"); + public static bool like(string str, string pattern) => call($"{ec.ParsedContent["str"]} LIKE {ec.ParsedContent["pattern"]}"); + public static bool not_like(string str, string pattern) => call($"{ec.ParsedContent["str"]} NOT LIKE {ec.ParsedContent["pattern"]}"); + public static bool ilike(string str, string pattern) => call($"{ec.ParsedContent["str"]} ILIKE {ec.ParsedContent["pattern"]}"); + public static bool not_ilike(string str, string pattern) => call($"{ec.ParsedContent["str"]} NOT ILIKE {ec.ParsedContent["pattern"]}"); + + public static bool within(object geo, object geohash1) => call($"{ec.ParsedContent["str"]} within({ec.ParsedContent["geohash1"]})"); + public static bool within(object geo, object geohash1, object geohash2) => call($"{ec.ParsedContent["str"]} within({ec.ParsedContent["geohash1"]},{ec.ParsedContent["geohash2"]})"); + public static bool within(object geo, object geohash1, object geohash2, object geohash3) => call($"{ec.ParsedContent["str"]} within({ec.ParsedContent["geohash1"]},{ec.ParsedContent["geohash2"]},{ec.ParsedContent["geohash3"]})"); + public static bool within(object geo, object geohash1, object geohash2, object geohash3, object geohash4) => call($"{ec.ParsedContent["str"]} within({ec.ParsedContent["geohash1"]},{ec.ParsedContent["geohash2"]},{ec.ParsedContent["geohash3"]},{ec.ParsedContent["geohash4"]})"); + public static bool within(object geo, object geohash1, object geohash2, object geohash3, object geohash4, object geohash5) => call($"{ec.ParsedContent["str"]} within({ec.ParsedContent["geohash1"]},{ec.ParsedContent["geohash2"]},{ec.ParsedContent["geohash3"]},{ec.ParsedContent["geohash4"]},{ec.ParsedContent["geohash5"]})"); +} + +partial class QuestDbGlobalExtensions +{ + /// + /// QuestDB lambda 表达式树扩展解析 + /// fsql.SelectLongSequence(10, () => new { str1 = qdbfunc.rnd_str(10, 5, 8, 0), ... })... + /// SELECT rnd_str(10,5,8,0) FROM long_sequence(10) + /// + public static ISelect SelectLongSequence(this IFreeSql fsql, long iterations, Expression> selector) + { + var selector2 = Expression.Lambda>(selector.Body, Expression.Parameter(typeof(object), "a")); + var tablename = $"(long_sequence ({iterations}))"; + return fsql.Select().AsTable((t, old) => tablename).WithTempQuery(selector2); + } + + /// + /// QuestDB lambda 表达式树扩展解析 + /// fsql.SelectTimestampSequence(10, () => new { str1 = qdbfunc.rnd_str(10, 5, 8, 0), ... })... + /// SELECT rnd_str(10,5,8,0) FROM long_sequence(10) + /// + public static ISelect SelectTimestampSequence(this IFreeSql fsql, DateTime startTimestamp, TimeSpan step, Expression> selector) + { + var selector2 = Expression.Lambda>(selector.Body, Expression.Parameter(typeof(object), "a")); + var tablename = $"(timestamp_sequence (to_timestamp('{startTimestamp.ToString("yyyy-MM-dd HH:mm:ss")}', 'yyyy-MM-dd HH:mm:ss'), {Math.Ceiling(step.TotalMilliseconds / 1000)}))"; + return fsql.Select().AsTable((t, old) => tablename).WithTempQuery(selector2); + } +} \ No newline at end of file diff --git a/Providers/FreeSql.Provider.QuestDb/QuestDbUtils.cs b/Providers/FreeSql.Provider.QuestDb/QuestDbUtils.cs index 3fca2e51..ea2c4147 100644 --- a/Providers/FreeSql.Provider.QuestDb/QuestDbUtils.cs +++ b/Providers/FreeSql.Provider.QuestDb/QuestDbUtils.cs @@ -28,8 +28,16 @@ namespace FreeSql.QuestDb { class QuestDbUtils : CommonUtils { + string now_to_timezone; public QuestDbUtils(IFreeSql orm) : base(orm) { + var timeOffset = (int)TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now).TotalMinutes; + if (timeOffset == 0) now_to_timezone = "systimestamp()"; + else + { + var absTimeOffset = Math.Abs(timeOffset); + now_to_timezone = $"to_timezone(systimestamp(),'{(timeOffset > 0 ? '+' : '-')}{(absTimeOffset / 60).ToString().PadLeft(2, '0')}:{(absTimeOffset % 60).ToString().PadLeft(2, '0')}')"; + } } static Array getParamterArrayValue(Type arrayType, object value, object defaultValue) @@ -267,8 +275,8 @@ namespace FreeSql.QuestDb public override string StringConcat(string[] objs, Type[] types) => $"{string.Join(" || ", objs)}"; public override string Mod(string left, string right, Type leftType, Type rightType) => $"{left} % {right}"; public override string Div(string left, string right, Type leftType, Type rightType) => $"{left} / {right}"; - public override string Now => "sysdate"; - public override string NowUtc => "systimestamp"; + public override string Now => now_to_timezone; + public override string NowUtc => "systimestamp()"; public override string QuoteWriteParamterAdapter(Type type, string paramterName) => paramterName; protected override string QuoteReadColumnAdapter(Type type, Type mapType, string columnName) => columnName; @@ -281,44 +289,6 @@ namespace FreeSql.QuestDb value = getParamterValue(type, value); var type2 = value.GetType(); if (type2 == typeof(byte[])) return $"'\\x{CommonUtils.BytesSqlRaw(value as byte[])}'"; - if (type2 == typeof(TimeSpan) || type2 == typeof(TimeSpan?)) - { - var ts = (TimeSpan)value; - return $"'{Math.Min(24, (int)Math.Floor(ts.TotalHours))}:{ts.Minutes}:{ts.Seconds}'"; - } - else if (value is Array) - { - var valueArr = value as Array; - var eleType = type2.GetElementType(); - var len = valueArr.GetLength(0); - var sb = new StringBuilder().Append("ARRAY["); - for (var a = 0; a < len; a++) - { - var item = valueArr.GetValue(a); - if (a > 0) sb.Append(","); - sb.Append(GetNoneParamaterSqlValue(specialParams, specialParamFlag, col, eleType, item)); - } - - sb.Append("]"); - var dbinfo = _orm.CodeFirst.GetDbInfo(type); - if (dbinfo != null) sb.Append("::").Append(dbinfo.dbtype); - return sb.ToString(); - } - else if (type2 == typeof(BitArray)) - { - return $"'{To1010(value as BitArray)}'"; - } - else if (type2 == typeof(NpgsqlLine) || type2 == typeof(NpgsqlLine?)) - { - var line = value.ToString(); - return line == "{0,0,0}" ? "'{0,-1,-1}'" : $"'{line}'"; - } - else if (type2 == typeof((IPAddress Address, int Subnet)) || - type2 == typeof((IPAddress Address, int Subnet)?)) - { - var cidr = ((IPAddress Address, int Subnet))value; - return $"'{cidr.Address}/{cidr.Subnet}'"; - } else if (dicGetParamterValue.ContainsKey(type2.FullName)) { value = string.Concat(value); @@ -326,13 +296,6 @@ namespace FreeSql.QuestDb return FormatSql("{0}", value, 1); } - - string To1010(BitArray ba) - { - char[] ret = new char[ba.Length]; - for (int a = 0; a < ba.Length; a++) ret[a] = ba[a] ? '1' : '0'; - return new string(ret); - } } }