diff --git a/FreeSql.DbContext/FreeSql.DbContext.xml b/FreeSql.DbContext/FreeSql.DbContext.xml index dc0203b8..d9f91124 100644 --- a/FreeSql.DbContext/FreeSql.DbContext.xml +++ b/FreeSql.DbContext/FreeSql.DbContext.xml @@ -110,6 +110,13 @@ 清空状态数据 + + + 根据 lambda 条件删除数据 + + + + 添加 diff --git a/FreeSql.Tests/FreeSql.Tests/UnitTest2.cs b/FreeSql.Tests/FreeSql.Tests/UnitTest2.cs index 3cb9aa53..82594730 100644 --- a/FreeSql.Tests/FreeSql.Tests/UnitTest2.cs +++ b/FreeSql.Tests/FreeSql.Tests/UnitTest2.cs @@ -245,6 +245,8 @@ namespace FreeSql.Tests [Fact] public void Test02() { + g.sqlite.Update(Guid.Empty).Set(a => a.ct1 == a.ct2).ExecuteAffrows(); + g.sqlite.Insert(new otot1 { name = "otot1_name1" }).ExecuteAffrows(); var otolst1 = g.sqlite.Select() diff --git a/FreeSql/FreeSql.xml b/FreeSql/FreeSql.xml index 9c96313b..a89a4d64 100644 --- a/FreeSql/FreeSql.xml +++ b/FreeSql/FreeSql.xml @@ -2267,124 +2267,103 @@ CodeFirst迁移,执行完成触发 - - - + - Insert/Update自动值处理 + 不使用命令参数化执行,针对 Insert/Update - + - 内置解析功能,可辅助您进行解析 + 是否生成命令参数化执行,针对 lambda 表达式解析 - + - 需要您解析的表达式 + 延时加载导航属性对象,导航属性需要声明 virtual - + - 解析后的内容 + 将实体类型与数据库对比,返回DDL语句 + + - + - 实体类型 + 将实体类型集合与数据库对比,返回DDL语句 + 实体类型 + - + - 实体配置 + 将实体类型与数据库对比,返回DDL语句(指定表名) + 实体类型 + 指定表名对比 + - + - 索引配置 + 同步实体类型到数据库 + + - + - 实体类型 + 同步实体类型集合到数据库 + + - + - 实体的属性 + 同步实体类型到数据库(指定表名) + 实体类型 + 指定表名对比 + - + - 实体的属性配置 + 根据 System.Type 获取数据库信息 + + - + - 标识符,可将 CurdBefore 与 CurdAfter 进行匹配 + 在外部配置实体的特性 + + + - + - 操作类型 + 在外部配置实体的特性 + + + - + - 实体类型 + 获取在外部配置实体的特性 + + 未使用ConfigEntity配置时,返回null - + - 实体类型的元数据 + 获取实体类核心配置 + + - - - 执行的 SQL - - - - - 参数化命令 - - - - - 发生的错误 - - - - - 执行SQL命令,返回的结果 - - - - - 耗时(单位:Ticks) - - - - - 耗时(单位:毫秒) - - - - - 标识符,可将 SyncStructureBeforeEventArgs 与 SyncStructureAfterEventArgs 进行匹配 - - - - - 实体类型 - - - - - 执行的 SQL - - - + 获取所有数据库 @@ -2577,158 +2556,6 @@ - - - 获取c#类型对象 - - - - - - - 获取ado.net读取方法, GetBoolean、GetInt64 - - - - - - - 序列化 - - - - - - - 反序列化 - - - - - - - 获取数据库枚举类型,适用 PostgreSQL - - - - - - - AsType, Ctor, ClearData 三处地方需要重新加载 - - - - - AsType, Ctor, ClearData 三处地方需要重新加载 - - - - - 通过属性的注释文本,通过 xml 读取 - - - Dict:key=属性名,value=注释 - - - - 创建一个过滤器 - - - 名字 - 表达式 - - - - - 中间表,多对多 - - - - - 不进行任何处理 - - - - - 将帕斯卡命名字符串转换为下划线分隔字符串 - - BigApple -> Big_Apple - - - - - 将帕斯卡命名字符串转换为下划线分隔字符串,且转换为全大写 - - BigApple -> BIG_APPLE - - - - - 将帕斯卡命名字符串转换为下划线分隔字符串,且转换为全小写 - - BigApple -> big_apple - - - - - 将字符串转换为大写 - - BigApple -> BIGAPPLE - - - - - 将字符串转换为小写 - - BigApple -> bigapple - - - - - 将帕斯卡命名字符串转换为下划线分隔字符串 - - BigApple -> Big_Apple - - - - - - - C#: that >= between && that <= and - SQL: that BETWEEN between AND and - - - - - - - - - 注意:这个方法和 Between 有细微区别 - C#: that >= start && that < end - SQL: that >= start and that < end - - - - - - - - - C#:从元组集合中查找 exp1, exp2 是否存在 - SQL: - exp1 = that[0].Item1 and exp2 = that[0].Item2 OR - exp1 = that[1].Item1 and exp2 = that[1].Item2 OR - ... - 注意:当 that 为 null 或 empty 时,返回 1=0 - - - - - - - - C#:从元组集合中查找 exp1, exp2, exp2 是否存在 @@ -2991,6 +2818,160 @@ CodeFirst 模式开发相关方法 + + + DbFirst 模式开发相关方法 + + < + + + true 时生效 + + + + + + 使用 or 拼接两个 lambda 表达式 + + + + + + 使用 or 拼接两个 lambda 表达式 + + + + true 时生效 + + + + + + 将 lambda 表达式取反 + + + + true 时生效 + + + + + 生成类似Mongodb的ObjectId有序、不重复Guid + + + + + + 插入数据 + + + + + + + 插入数据,传入实体 + + + + + + + + 插入数据,传入实体数组 + + + + + + + + 插入数据,传入实体集合 + + + + + + + + 插入数据,传入实体集合 + + + + + + + + 修改数据 + + + + + + + 修改数据,传入动态对象如:主键值 | new[]{主键值1,主键值2} | TEntity1 | new[]{TEntity1,TEntity2} | new{id=1} + + + 主键值、主键值集合、实体、实体集合、匿名对象、匿名对象集合 + + + + + 查询数据 + + + + + + + 查询数据,传入动态对象如:主键值 | new[]{主键值1,主键值2} | TEntity1 | new[]{TEntity1,TEntity2} | new{id=1} + + + 主键值、主键值集合、实体、实体集合、匿名对象、匿名对象集合 + + + + + 删除数据 + + + + + + + 删除数据,传入动态对象如:主键值 | new[]{主键值1,主键值2} | TEntity1 | new[]{TEntity1,TEntity2} | new{id=1} + + + 主键值、主键值集合、实体、实体集合、匿名对象、匿名对象集合 + + + + + 开启事务(不支持异步),60秒未执行完将自动提交 + + 事务体 () => {} + + + + 开启事务(不支持异步) + + 事务体 () => {} + 超时,未执行完将自动提交 + + + + 数据库访问对象 + + + + + 所有拦截方法都在这里 + + + + + CodeFirst 模式开发相关方法 + + DbFirst 模式开发相关方法 diff --git a/Providers/FreeSql.Provider.Odbc/Dameng/OdbcDamengAdo/OdbcDamengConnectionPool.cs b/Providers/FreeSql.Provider.Odbc/Dameng/OdbcDamengAdo/OdbcDamengConnectionPool.cs index 61bdc72c..81a601bb 100644 --- a/Providers/FreeSql.Provider.Odbc/Dameng/OdbcDamengAdo/OdbcDamengConnectionPool.cs +++ b/Providers/FreeSql.Provider.Odbc/Dameng/OdbcDamengAdo/OdbcDamengConnectionPool.cs @@ -62,7 +62,7 @@ namespace FreeSql.Odbc.Dameng { internal OdbcDamengConnectionPool _pool; - public string Name { get; set; } = "Oracle OdbcConnection 对象池"; + public string Name { get; set; } = "Dameng OdbcConnection 对象池"; public int PoolSize { get; set; } = 100; public TimeSpan SyncGetTimeout { get; set; } = TimeSpan.FromSeconds(10); public TimeSpan IdleTimeout { get; set; } = TimeSpan.Zero; diff --git a/Providers/FreeSql.Provider.Odbc/GBase/Curd/OdbcGBaseDelete.cs b/Providers/FreeSql.Provider.Odbc/GBase/Curd/OdbcGBaseDelete.cs new file mode 100644 index 00000000..f2403466 --- /dev/null +++ b/Providers/FreeSql.Provider.Odbc/GBase/Curd/OdbcGBaseDelete.cs @@ -0,0 +1,31 @@ +using FreeSql.Internal; +using System; +using System.Collections.Generic; +using System.Data; +using System.Text; +using System.Threading.Tasks; + +namespace FreeSql.Odbc.GBase +{ + + class OdbcGBaseDelete : Internal.CommonProvider.DeleteProvider where T1 : class + { + public OdbcGBaseDelete(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) + : base(orm, commonUtils, commonExpression, dywhere) + { + } + + public override List ExecuteDeleted() + { + throw new NotImplementedException(); + } + +#if net40 +#else + public override Task> ExecuteDeletedAsync() + { + throw new NotImplementedException(); + } +#endif + } +} diff --git a/Providers/FreeSql.Provider.Odbc/GBase/Curd/OdbcGBaseSelect.cs b/Providers/FreeSql.Provider.Odbc/GBase/Curd/OdbcGBaseSelect.cs new file mode 100644 index 00000000..b34ea8c5 --- /dev/null +++ b/Providers/FreeSql.Provider.Odbc/GBase/Curd/OdbcGBaseSelect.cs @@ -0,0 +1,176 @@ +using FreeSql.Internal; +using FreeSql.Internal.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; + +namespace FreeSql.Odbc.GBase +{ + + class OdbcGBaseSelect : FreeSql.Internal.CommonProvider.Select1Provider where T1 : class + { + + internal static string ToSqlStatic(CommonUtils _commonUtils, CommonExpression _commonExpression, string _select, bool _distinct, string field, StringBuilder _join, StringBuilder _where, string _groupby, string _having, string _orderby, int _skip, int _limit, List _tables, List> tbUnions, Func _aliasRule, List _whereCascadeExpression, IFreeSql _orm) + { + if (_orm.CodeFirst.IsAutoSyncStructure) + _orm.CodeFirst.SyncStructure(_tables.Select(a => a.Table.Type).ToArray()); + + if (_whereCascadeExpression.Any()) + foreach (var tb in _tables.Where(a => a.Type != SelectTableInfoType.Parent)) + tb.Cascade = _commonExpression.GetWhereCascadeSql(tb, _whereCascadeExpression); + + var sb = new StringBuilder(); + var tbUnionsGt0 = tbUnions.Count > 1; + for (var tbUnionsIdx = 0; tbUnionsIdx < tbUnions.Count; tbUnionsIdx++) + { + if (tbUnionsIdx > 0) sb.Append(" \r\n\r\nUNION ALL\r\n\r\n"); + if (tbUnionsGt0) sb.Append(_select).Append(" * from ("); + var tbUnion = tbUnions[tbUnionsIdx]; + + var sbnav = new StringBuilder(); + sb.Append(_select); + + if (_skip > 0) + sb.Append("SKIP ").Append(_skip).Append(" "); + if (_limit > 0) + sb.Append("FIRST ").Append(_limit).Append(" "); + + if (_distinct) sb.Append("DISTINCT "); + sb.Append(field).Append(" \r\nFROM "); + var tbsjoin = _tables.Where(a => a.Type != SelectTableInfoType.From).ToArray(); + var tbsfrom = _tables.Where(a => a.Type == SelectTableInfoType.From).ToArray(); + for (var a = 0; a < tbsfrom.Length; a++) + { + sb.Append(_commonUtils.QuoteSqlName(tbUnion[tbsfrom[a].Table.Type])).Append(" ").Append(_aliasRule?.Invoke(tbsfrom[a].Table.Type, tbsfrom[a].Alias) ?? tbsfrom[a].Alias); + if (tbsjoin.Length > 0) + { + //如果存在 join 查询,则处理 from t1, t2 改为 from t1 inner join t2 on 1 = 1 + for (var b = 1; b < tbsfrom.Length; b++) + { + sb.Append(" \r\nLEFT JOIN ").Append(_commonUtils.QuoteSqlName(tbUnion[tbsfrom[b].Table.Type])).Append(" ").Append(_aliasRule?.Invoke(tbsfrom[b].Table.Type, tbsfrom[b].Alias) ?? tbsfrom[b].Alias); + + if (string.IsNullOrEmpty(tbsfrom[b].NavigateCondition) && string.IsNullOrEmpty(tbsfrom[b].On) && string.IsNullOrEmpty(tbsfrom[b].Cascade)) sb.Append(" ON 1 = 1"); + else + { + var onSql = tbsfrom[b].NavigateCondition ?? tbsfrom[b].On; + sb.Append(" ON ").Append(onSql); + if (string.IsNullOrEmpty(tbsfrom[b].Cascade) == false) + { + if (string.IsNullOrEmpty(onSql)) sb.Append(tbsfrom[b].Cascade); + else sb.Append(" AND (").Append(tbsfrom[b].Cascade).Append(")"); + } + } + } + break; + } + else + { + if (!string.IsNullOrEmpty(tbsfrom[a].NavigateCondition)) sbnav.Append(" AND (").Append(tbsfrom[a].NavigateCondition).Append(")"); + if (!string.IsNullOrEmpty(tbsfrom[a].On)) sbnav.Append(" AND (").Append(tbsfrom[a].On).Append(")"); + if (a > 0 && !string.IsNullOrEmpty(tbsfrom[a].Cascade)) sbnav.Append(" AND (").Append(tbsfrom[a].Cascade).Append(")"); + } + if (a < tbsfrom.Length - 1) sb.Append(", "); + } + foreach (var tb in tbsjoin) + { + if (tb.Type == SelectTableInfoType.Parent) continue; + switch (tb.Type) + { + case SelectTableInfoType.LeftJoin: + sb.Append(" \r\nLEFT JOIN "); + break; + case SelectTableInfoType.InnerJoin: + sb.Append(" \r\nINNER JOIN "); + break; + case SelectTableInfoType.RightJoin: + sb.Append(" \r\nRIGHT JOIN "); + break; + } + sb.Append(_commonUtils.QuoteSqlName(tbUnion[tb.Table.Type])).Append(" ").Append(_aliasRule?.Invoke(tb.Table.Type, tb.Alias) ?? tb.Alias).Append(" ON ").Append(tb.On ?? tb.NavigateCondition); + if (!string.IsNullOrEmpty(tb.Cascade)) sb.Append(" AND (").Append(tb.Cascade).Append(")"); + if (!string.IsNullOrEmpty(tb.On) && !string.IsNullOrEmpty(tb.NavigateCondition)) sbnav.Append(" AND (").Append(tb.NavigateCondition).Append(")"); + } + if (_join.Length > 0) sb.Append(_join); + + sbnav.Append(_where); + if (!string.IsNullOrEmpty(_tables[0].Cascade)) + sbnav.Append(" AND (").Append(_tables[0].Cascade).Append(")"); + + if (sbnav.Length > 0) + { + sb.Append(" \r\nWHERE ").Append(sbnav.Remove(0, 5)); + } + if (string.IsNullOrEmpty(_groupby) == false) + { + sb.Append(_groupby); + if (string.IsNullOrEmpty(_having) == false) + sb.Append(" \r\nHAVING ").Append(_having.Substring(5)); + } + sb.Append(_orderby); + + sbnav.Clear(); + if (tbUnionsGt0) sb.Append(") ftb"); + } + return sb.ToString(); + } + + public OdbcGBaseSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override ISelect From(Expression, T2, ISelectFromExpression>> exp) { this.InternalFrom(exp); var ret = new OdbcPostgreSQLSelect(_orm, _commonUtils, _commonExpression, null); OdbcGBaseSelect.CopyData(this, ret, exp?.Parameters); return ret; } + public override ISelect From(Expression, T2, T3, ISelectFromExpression>> exp) { this.InternalFrom(exp); var ret = new OdbcPostgreSQLSelect(_orm, _commonUtils, _commonExpression, null); OdbcGBaseSelect.CopyData(this, ret, exp?.Parameters); return ret; } + public override ISelect From(Expression, T2, T3, T4, ISelectFromExpression>> exp) { this.InternalFrom(exp); var ret = new OdbcPostgreSQLSelect(_orm, _commonUtils, _commonExpression, null); OdbcGBaseSelect.CopyData(this, ret, exp?.Parameters); return ret; } + public override ISelect From(Expression, T2, T3, T4, T5, ISelectFromExpression>> exp) { this.InternalFrom(exp); var ret = new OdbcPostgreSQLSelect(_orm, _commonUtils, _commonExpression, null); OdbcGBaseSelect.CopyData(this, ret, exp?.Parameters); return ret; } + public override ISelect From(Expression, T2, T3, T4, T5, T6, ISelectFromExpression>> exp) { this.InternalFrom(exp); var ret = new OdbcPostgreSQLSelect(_orm, _commonUtils, _commonExpression, null); OdbcGBaseSelect.CopyData(this, ret, exp?.Parameters); return ret; } + public override ISelect From(Expression, T2, T3, T4, T5, T6, T7, ISelectFromExpression>> exp) { this.InternalFrom(exp); var ret = new OdbcPostgreSQLSelect(_orm, _commonUtils, _commonExpression, null); OdbcGBaseSelect.CopyData(this, ret, exp?.Parameters); return ret; } + public override ISelect From(Expression, T2, T3, T4, T5, T6, T7, T8, ISelectFromExpression>> exp) { this.InternalFrom(exp); var ret = new OdbcPostgreSQLSelect(_orm, _commonUtils, _commonExpression, null); OdbcGBaseSelect.CopyData(this, ret, exp?.Parameters); return ret; } + public override ISelect From(Expression, T2, T3, T4, T5, T6, T7, T8, T9, ISelectFromExpression>> exp) { this.InternalFrom(exp); var ret = new OdbcPostgreSQLSelect(_orm, _commonUtils, _commonExpression, null); OdbcGBaseSelect.CopyData(this, ret, exp?.Parameters); return ret; } + public override ISelect From(Expression, T2, T3, T4, T5, T6, T7, T8, T9, T10, ISelectFromExpression>> exp) { this.InternalFrom(exp); var ret = new OdbcPostgreSQLSelect(_orm, _commonUtils, _commonExpression, null); OdbcGBaseSelect.CopyData(this, ret, exp?.Parameters); return ret; } + public override string ToSql(string field = null) => ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _whereCascadeExpression, _orm); + } + class OdbcPostgreSQLSelect : FreeSql.Internal.CommonProvider.Select2Provider where T1 : class where T2 : class + { + public OdbcPostgreSQLSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => OdbcGBaseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _whereCascadeExpression, _orm); + } + class OdbcPostgreSQLSelect : FreeSql.Internal.CommonProvider.Select3Provider where T1 : class where T2 : class where T3 : class + { + public OdbcPostgreSQLSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => OdbcGBaseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _whereCascadeExpression, _orm); + } + class OdbcPostgreSQLSelect : FreeSql.Internal.CommonProvider.Select4Provider where T1 : class where T2 : class where T3 : class where T4 : class + { + public OdbcPostgreSQLSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => OdbcGBaseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _whereCascadeExpression, _orm); + } + class OdbcPostgreSQLSelect : FreeSql.Internal.CommonProvider.Select5Provider where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class + { + public OdbcPostgreSQLSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => OdbcGBaseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _whereCascadeExpression, _orm); + } + class OdbcPostgreSQLSelect : FreeSql.Internal.CommonProvider.Select6Provider where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class + { + public OdbcPostgreSQLSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => OdbcGBaseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _whereCascadeExpression, _orm); + } + class OdbcPostgreSQLSelect : FreeSql.Internal.CommonProvider.Select7Provider where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class + { + public OdbcPostgreSQLSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => OdbcGBaseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _whereCascadeExpression, _orm); + } + class OdbcPostgreSQLSelect : FreeSql.Internal.CommonProvider.Select8Provider where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class where T8 : class + { + public OdbcPostgreSQLSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => OdbcGBaseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _whereCascadeExpression, _orm); + } + class OdbcPostgreSQLSelect : FreeSql.Internal.CommonProvider.Select9Provider where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class where T8 : class where T9 : class + { + public OdbcPostgreSQLSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => OdbcGBaseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _whereCascadeExpression, _orm); + } + class OdbcPostgreSQLSelect : FreeSql.Internal.CommonProvider.Select10Provider where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class where T8 : class where T9 : class where T10 : class + { + public OdbcPostgreSQLSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) : base(orm, commonUtils, commonExpression, dywhere) { } + public override string ToSql(string field = null) => OdbcGBaseSelect.ToSqlStatic(_commonUtils, _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, _whereCascadeExpression, _orm); + } +} diff --git a/Providers/FreeSql.Provider.Odbc/GBase/Curd/__OdbcGBaseInsert.cs b/Providers/FreeSql.Provider.Odbc/GBase/Curd/__OdbcGBaseInsert.cs new file mode 100644 index 00000000..496108fa --- /dev/null +++ b/Providers/FreeSql.Provider.Odbc/GBase/Curd/__OdbcGBaseInsert.cs @@ -0,0 +1,149 @@ +using FreeSql.Internal; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FreeSql.Odbc.GBase +{ + + class OdbcGBaseInsert : Internal.CommonProvider.InsertProvider where T1 : class + { + public OdbcGBaseInsert(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression) + : base(orm, commonUtils, commonExpression) + { + } + + public override int ExecuteAffrows() => base.SplitExecuteAffrows(5000, 3000); + public override long ExecuteIdentity() => base.SplitExecuteIdentity(5000, 3000); + public override List ExecuteInserted() => base.SplitExecuteInserted(5000, 3000); + + protected override long RawExecuteIdentity() + { + var sql = this.ToSql(); + if (string.IsNullOrEmpty(sql)) return 0; + + long ret = 0; + Exception exception = null; + Aop.CurdBeforeEventArgs before = null; + + var identCols = _table.Columns.Where(a => a.Value.Attribute.IsIdentity == true); + if (identCols.Any() == false) + { + before = new Aop.CurdBeforeEventArgs(_table.Type, _table, Aop.CurdType.Insert, sql, _params); + _orm.Aop.CurdBefore?.Invoke(this, before); + try + { + ret = _orm.Ado.ExecuteNonQuery(_connection, _transaction, CommandType.Text, sql, _params); + } + catch (Exception ex) + { + exception = ex; + throw ex; + } + finally + { + var after = new Aop.CurdAfterEventArgs(before, exception, ret); + _orm.Aop.CurdAfter?.Invoke(this, after); + } + return 0; + } + sql = string.Concat(sql, " RETURNING ", _commonUtils.QuoteSqlName(identCols.First().Value.Attribute.Name)); + before = new Aop.CurdBeforeEventArgs(_table.Type, _table, Aop.CurdType.Insert, sql, _params); + _orm.Aop.CurdBefore?.Invoke(this, before); + try + { + long.TryParse(string.Concat(_orm.Ado.ExecuteScalar(_connection, _transaction, CommandType.Text, sql, _params)), out ret); + } + catch (Exception ex) + { + exception = ex; + throw ex; + } + finally + { + var after = new Aop.CurdAfterEventArgs(before, exception, ret); + _orm.Aop.CurdAfter?.Invoke(this, after); + } + return ret; + } + + protected override List RawExecuteInserted() + { + var sql = this.ToSql(); + if (string.IsNullOrEmpty(sql)) return new List(); + + var ret = _source.ToList(); + this.RawExecuteAffrows(); + return ret; + } + +#if net40 +#else + public override Task ExecuteAffrowsAsync() => base.SplitExecuteAffrowsAsync(5000, 3000); + public override Task ExecuteIdentityAsync() => base.SplitExecuteIdentityAsync(5000, 3000); + public override Task> ExecuteInsertedAsync() => base.SplitExecuteInsertedAsync(5000, 3000); + + async protected override Task RawExecuteIdentityAsync() + { + var sql = this.ToSql(); + if (string.IsNullOrEmpty(sql)) return 0; + + long ret = 0; + Exception exception = null; + Aop.CurdBeforeEventArgs before = null; + + var identCols = _table.Columns.Where(a => a.Value.Attribute.IsIdentity == true); + if (identCols.Any() == false) + { + before = new Aop.CurdBeforeEventArgs(_table.Type, _table, Aop.CurdType.Insert, sql, _params); + _orm.Aop.CurdBefore?.Invoke(this, before); + try + { + ret = await _orm.Ado.ExecuteNonQueryAsync(_connection, _transaction, CommandType.Text, sql, _params); + } + catch (Exception ex) + { + exception = ex; + throw ex; + } + finally + { + var after = new Aop.CurdAfterEventArgs(before, exception, ret); + _orm.Aop.CurdAfter?.Invoke(this, after); + } + return 0; + } + sql = string.Concat(sql, " RETURNING ", _commonUtils.QuoteSqlName(identCols.First().Value.Attribute.Name)); + before = new Aop.CurdBeforeEventArgs(_table.Type, _table, Aop.CurdType.Insert, sql, _params); + _orm.Aop.CurdBefore?.Invoke(this, before); + try + { + long.TryParse(string.Concat(await _orm.Ado.ExecuteScalarAsync(_connection, _transaction, CommandType.Text, sql, _params)), out ret); + } + catch (Exception ex) + { + exception = ex; + throw ex; + } + finally + { + var after = new Aop.CurdAfterEventArgs(before, exception, ret); + _orm.Aop.CurdAfter?.Invoke(this, after); + } + return ret; + } + async protected override Task> RawExecuteInsertedAsync() + { + var sql = this.ToSql(); + if (string.IsNullOrEmpty(sql)) return new List(); + + var ret = _source.ToList(); + await this.RawExecuteAffrowsAsync(); + return ret; + } +#endif + } +} diff --git a/Providers/FreeSql.Provider.Odbc/GBase/Curd/__OdbcGBaseUpdate.cs b/Providers/FreeSql.Provider.Odbc/GBase/Curd/__OdbcGBaseUpdate.cs new file mode 100644 index 00000000..dc9d3cc7 --- /dev/null +++ b/Providers/FreeSql.Provider.Odbc/GBase/Curd/__OdbcGBaseUpdate.cs @@ -0,0 +1,85 @@ +using FreeSql.Internal; +using FreeSql.Internal.Model; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FreeSql.Odbc.GBase +{ + + class OdbcGBaseUpdate : Internal.CommonProvider.UpdateProvider where T1 : class + { + + public OdbcGBaseUpdate(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) + : base(orm, commonUtils, commonExpression, dywhere) + { + } + + public override int ExecuteAffrows() => base.SplitExecuteAffrows(500, 3000); + public override List ExecuteUpdated() => base.SplitExecuteUpdated(500, 3000); + + protected override List RawExecuteUpdated() + { + throw new NotImplementedException(); + } + + protected override void ToSqlCase(StringBuilder caseWhen, ColumnInfo[] primarys) + { + if (_table.Primarys.Length == 1) + { + caseWhen.Append(_commonUtils.QuoteReadColumn(_table.Primarys.First().Attribute.MapType, _commonUtils.QuoteSqlName(_table.Primarys.First().Attribute.Name))); + return; + } + caseWhen.Append("("); + var pkidx = 0; + foreach (var pk in _table.Primarys) + { + if (pkidx > 0) caseWhen.Append(" || "); + caseWhen.Append(_commonUtils.QuoteReadColumn(pk.Attribute.MapType, _commonUtils.QuoteSqlName(pk.Attribute.Name))).Append("::varchar"); + ++pkidx; + } + caseWhen.Append(")"); + } + + protected override void ToSqlWhen(StringBuilder sb, ColumnInfo[] primarys, object d) + { + if (_table.Primarys.Length == 1) + { + sb.Append(_commonUtils.FormatSql("{0}", _table.Primarys.First().GetMapValue(d))); + return; + } + sb.Append("("); + var pkidx = 0; + foreach (var pk in _table.Primarys) + { + if (pkidx > 0) sb.Append(" || "); + sb.Append(_commonUtils.FormatSql("{0}", pk.GetMapValue(d))).Append("::varchar"); + ++pkidx; + } + sb.Append(")"); + } + + protected override void ToSqlCaseWhenEnd(StringBuilder sb, ColumnInfo col) + { + if (_noneParameter == false) return; + var dbtype = _commonUtils.CodeFirst.GetDbInfo(col.Attribute.MapType)?.dbtype; + if (dbtype == null) return; + + sb.Append("::").Append(dbtype); + } + +#if net40 +#else + public override Task ExecuteAffrowsAsync() => base.SplitExecuteAffrowsAsync(500, 3000); + public override Task> ExecuteUpdatedAsync() => base.SplitExecuteUpdatedAsync(500, 3000); + + protected override Task> RawExecuteUpdatedAsync() + { + throw new NotImplementedException(); + } +#endif + } +} diff --git a/Providers/FreeSql.Provider.Odbc/GBase/OdbcGBaseAdo/OdbcGBaseConnectionPool.cs b/Providers/FreeSql.Provider.Odbc/GBase/OdbcGBaseAdo/OdbcGBaseConnectionPool.cs new file mode 100644 index 00000000..e9c6fae6 --- /dev/null +++ b/Providers/FreeSql.Provider.Odbc/GBase/OdbcGBaseAdo/OdbcGBaseConnectionPool.cs @@ -0,0 +1,248 @@ +using SafeObjectPool; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Data.Odbc; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace FreeSql.Odbc.GBase +{ + + class OdbcGBaseConnectionPool : ObjectPool + { + + internal Action availableHandler; + internal Action unavailableHandler; + + public OdbcGBaseConnectionPool(string name, string connectionString, Action availableHandler, Action unavailableHandler) : base(null) + { + var policy = new OdbcPostgreSQLConnectionPoolPolicy + { + _pool = this, + Name = name + }; + this.Policy = policy; + policy.ConnectionString = connectionString; + + this.availableHandler = availableHandler; + this.unavailableHandler = unavailableHandler; + } + + public void Return(Object obj, Exception exception, bool isRecreate = false) + { + if (exception != null && exception is OdbcException) + { + + if (exception is System.IO.IOException) + { + + base.SetUnavailable(exception); + + } + else if (obj.Value.Ping() == false) + { + + base.SetUnavailable(exception); + } + } + base.Return(obj, isRecreate); + } + } + + class OdbcPostgreSQLConnectionPoolPolicy : IPolicy + { + + internal OdbcGBaseConnectionPool _pool; + public string Name { get; set; } = "GBase OdbcConnection 对象池"; + public int PoolSize { get; set; } = 50; + public TimeSpan SyncGetTimeout { get; set; } = TimeSpan.FromSeconds(10); + public TimeSpan IdleTimeout { get; set; } = TimeSpan.Zero; + public int AsyncGetCapacity { get; set; } = 10000; + public bool IsThrowGetTimeoutException { get; set; } = true; + public int CheckAvailableInterval { get; set; } = 5; + + static ConcurrentDictionary dicConnStrIncr = new ConcurrentDictionary(StringComparer.CurrentCultureIgnoreCase); + private string _connectionString; + public string ConnectionString + { + get => _connectionString; + set + { + _connectionString = value ?? ""; + + var pattern = @"Max\s*pool\s*size\s*=\s*(\d+)"; + Match m = Regex.Match(_connectionString, pattern, RegexOptions.IgnoreCase); + if (m.Success == false || int.TryParse(m.Groups[1].Value, out var poolsize) == false || poolsize <= 0) poolsize = 100; + var connStrIncr = dicConnStrIncr.AddOrUpdate(_connectionString, 1, (oldkey, oldval) => oldval + 1); + PoolSize = poolsize + connStrIncr; + _connectionString = m.Success ? + Regex.Replace(_connectionString, pattern, $"Max pool size={PoolSize}", RegexOptions.IgnoreCase) : + $"{_connectionString};Max pool size={PoolSize}"; + + pattern = @"Connection\s*LifeTime\s*=\s*(\d+)"; + m = Regex.Match(_connectionString, pattern, RegexOptions.IgnoreCase); + if (m.Success) + { + IdleTimeout = TimeSpan.FromSeconds(int.Parse(m.Groups[1].Value)); + _connectionString = Regex.Replace(_connectionString, pattern, "", RegexOptions.IgnoreCase); + } + + var minPoolSize = 0; + pattern = @"Min\s*pool\s*size\s*=\s*(\d+)"; + m = Regex.Match(_connectionString, pattern, RegexOptions.IgnoreCase); + if (m.Success) + { + minPoolSize = int.Parse(m.Groups[1].Value); + _connectionString = Regex.Replace(_connectionString, pattern, "", RegexOptions.IgnoreCase); + } + + FreeSql.Internal.CommonUtils.PrevReheatConnectionPool(_pool, minPoolSize); + } + } + + public bool OnCheckAvailable(Object obj) + { + if (obj.Value.State == ConnectionState.Closed) obj.Value.Open(); + return obj.Value.Ping(true); + } + + public DbConnection OnCreate() + { + var conn = new OdbcConnection(_connectionString); + return conn; + } + + public void OnDestroy(DbConnection obj) + { + if (obj.State != ConnectionState.Closed) obj.Close(); + obj.Dispose(); + } + + public void OnGet(Object obj) + { + + if (_pool.IsAvailable) + { + if (obj.Value == null) + { + if (_pool.SetUnavailable(new Exception("连接字符串错误")) == true) + throw new Exception($"【{this.Name}】连接字符串错误,请检查。"); + return; + } + + if (obj.Value.State != ConnectionState.Open || DateTime.Now.Subtract(obj.LastReturnTime).TotalSeconds > 60 && obj.Value.Ping() == false) + { + + try + { + obj.Value.Open(); + } + catch (Exception ex) + { + if (_pool.SetUnavailable(ex) == true) + throw new Exception($"【{this.Name}】状态不可用,等待后台检查程序恢复方可使用。{ex.Message}"); + } + } + } + } + +#if net40 +#else + async public Task OnGetAsync(Object obj) + { + + if (_pool.IsAvailable) + { + if (obj.Value == null) + { + if (_pool.SetUnavailable(new Exception("连接字符串错误")) == true) + throw new Exception($"【{this.Name}】连接字符串错误,请检查。"); + return; + } + + if (obj.Value.State != ConnectionState.Open || DateTime.Now.Subtract(obj.LastReturnTime).TotalSeconds > 60 && (await obj.Value.PingAsync()) == false) + { + + try + { + await obj.Value.OpenAsync(); + } + catch (Exception ex) + { + if (_pool.SetUnavailable(ex) == true) + throw new Exception($"【{this.Name}】状态不可用,等待后台检查程序恢复方可使用。{ex.Message}"); + } + } + } + } +#endif + + public void OnGetTimeout() + { + + } + + public void OnReturn(Object obj) + { + + } + + public void OnAvailable() + { + _pool.availableHandler?.Invoke(); + } + + public void OnUnavailable() + { + _pool.unavailableHandler?.Invoke(); + } + } + + static class DbConnectionExtensions + { + + static DbCommand PingCommand(DbConnection conn) + { + var cmd = conn.CreateCommand(); + cmd.CommandTimeout = 5; + cmd.CommandText = "select 1"; + return cmd; + } + public static bool Ping(this DbConnection that, bool isThrow = false) + { + try + { + PingCommand(that).ExecuteNonQuery(); + return true; + } + catch + { + if (that.State != ConnectionState.Closed) try { that.Close(); } catch { } + if (isThrow) throw; + return false; + } + } + +#if net40 +#else + async public static Task PingAsync(this DbConnection that, bool isThrow = false) + { + try + { + await PingCommand(that).ExecuteNonQueryAsync(); + return true; + } + catch + { + if (that.State != ConnectionState.Closed) try { that.Close(); } catch { } + if (isThrow) throw; + return false; + } + } +#endif + } +} diff --git a/Providers/FreeSql.Provider.Odbc/GBase/OdbcGBaseAdo/__OdbcGBaseAdo.cs b/Providers/FreeSql.Provider.Odbc/GBase/OdbcGBaseAdo/__OdbcGBaseAdo.cs new file mode 100644 index 00000000..c4c215f2 --- /dev/null +++ b/Providers/FreeSql.Provider.Odbc/GBase/OdbcGBaseAdo/__OdbcGBaseAdo.cs @@ -0,0 +1,68 @@ +using FreeSql.Internal; +using FreeSql.Internal.Model; +using SafeObjectPool; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data.Common; +using System.Data.Odbc; +using System.Text; +using System.Threading; + +namespace FreeSql.Odbc.GBase +{ + class OdbcGBaseAdo : FreeSql.Internal.CommonProvider.AdoProvider + { + public OdbcGBaseAdo() : base(DataType.PostgreSQL) { } + public OdbcGBaseAdo(CommonUtils util, string masterConnectionString, string[] slaveConnectionStrings) : base(DataType.PostgreSQL) + { + base._util = util; + if (!string.IsNullOrEmpty(masterConnectionString)) + MasterPool = new OdbcGBaseConnectionPool("主库", masterConnectionString, null, null); + if (slaveConnectionStrings != null) + { + foreach (var slaveConnectionString in slaveConnectionStrings) + { + var slavePool = new OdbcGBaseConnectionPool($"从库{SlavePools.Count + 1}", slaveConnectionString, () => Interlocked.Decrement(ref slaveUnavailables), () => Interlocked.Increment(ref slaveUnavailables)); + SlavePools.Add(slavePool); + } + } + } + + static DateTime dt1970 = new DateTime(1970, 1, 1); + public override object AddslashesProcessParam(object param, Type mapType, ColumnInfo mapColumn) + { + if (param == null) return "NULL"; + if (mapType != null && mapType != param.GetType() && (param is IEnumerable == false || mapType.IsArrayOrList())) + param = Utils.GetDataReaderValue(mapType, param); + if (param is bool || param is bool?) + return (bool)param ? "'t'" : "'f'"; + else if (param is string || param is char) + return string.Concat("'", param.ToString().Replace("'", "''"), "'"); + else if (param is Enum) + return ((Enum)param).ToInt64(); + else if (decimal.TryParse(string.Concat(param), out var trydec)) + return param; + 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; + else if (param is IEnumerable) + return AddslashesIEnumerable(param, mapType, mapColumn); + + return string.Concat("'", param.ToString().Replace("'", "''"), "'"); + } + + protected override DbCommand CreateCommand() + { + return new OdbcCommand(); + } + + protected override void ReturnConnection(ObjectPool pool, Object conn, Exception ex) + { + (pool as OdbcGBaseConnectionPool).Return(conn, ex); + } + + protected override DbParameter[] GetDbParamtersByObject(string sql, object obj) => _util.GetDbParamtersByObject(sql, obj); + } +} \ No newline at end of file diff --git a/Providers/FreeSql.Provider.Odbc/GBase/OdbcGBaseProvider.cs b/Providers/FreeSql.Provider.Odbc/GBase/OdbcGBaseProvider.cs new file mode 100644 index 00000000..eee74c06 --- /dev/null +++ b/Providers/FreeSql.Provider.Odbc/GBase/OdbcGBaseProvider.cs @@ -0,0 +1,61 @@ +using FreeSql.Internal; +using FreeSql.Internal.CommonProvider; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace FreeSql.Odbc.GBase +{ + + public class OdbcGBaseProvider : IFreeSql + { + + static OdbcGBaseProvider() + { + } + + public ISelect Select() where T1 : class => new OdbcGBaseSelect(this, this.InternalCommonUtils, this.InternalCommonExpression, null); + public ISelect Select(object dywhere) where T1 : class => new OdbcGBaseSelect(this, this.InternalCommonUtils, this.InternalCommonExpression, dywhere); + public IInsert Insert() where T1 : class => new OdbcGBaseInsert(this, this.InternalCommonUtils, this.InternalCommonExpression); + public IInsert Insert(T1 source) where T1 : class => this.Insert().AppendData(source); + public IInsert Insert(T1[] source) where T1 : class => this.Insert().AppendData(source); + public IInsert Insert(List source) where T1 : class => this.Insert().AppendData(source); + public IInsert Insert(IEnumerable source) where T1 : class => this.Insert().AppendData(source); + public IUpdate Update() where T1 : class => new OdbcGBaseUpdate(this, this.InternalCommonUtils, this.InternalCommonExpression, null); + public IUpdate Update(object dywhere) where T1 : class => new OdbcGBaseUpdate(this, this.InternalCommonUtils, this.InternalCommonExpression, dywhere); + public IDelete Delete() where T1 : class => new OdbcGBaseDelete(this, this.InternalCommonUtils, this.InternalCommonExpression, null); + public IDelete Delete(object dywhere) where T1 : class => new OdbcGBaseDelete(this, this.InternalCommonUtils, this.InternalCommonExpression, dywhere); + + public IAdo Ado { get; } + public IAop Aop { get; } + public ICodeFirst CodeFirst { get; } + public IDbFirst DbFirst { get; } + public OdbcGBaseProvider(string masterConnectionString, string[] slaveConnectionString) + { + this.InternalCommonUtils = new OdbcGBaseUtils(this); + this.InternalCommonExpression = new OdbcGBaseExpression(this.InternalCommonUtils); + + this.Ado = new OdbcGBaseAdo(this.InternalCommonUtils, masterConnectionString, slaveConnectionString); + this.Aop = new AopProvider(); + + this.DbFirst = new OdbcGBaseDbFirst(this, this.InternalCommonUtils, this.InternalCommonExpression); + this.CodeFirst = new OdbcGBaseCodeFirst(this, this.InternalCommonUtils, this.InternalCommonExpression); + } + + internal CommonUtils InternalCommonUtils { get; } + internal CommonExpression InternalCommonExpression { get; } + + public void Transaction(Action handler) => Ado.Transaction(handler); + public void Transaction(Action handler, TimeSpan timeout) => Ado.Transaction(handler, timeout); + + public GlobalFilter GlobalFilter { get; } = new GlobalFilter(); + + ~OdbcGBaseProvider() => this.Dispose(); + int _disposeCounter; + public void Dispose() + { + if (Interlocked.Increment(ref _disposeCounter) != 1) return; + (this.Ado as AdoProvider)?.Dispose(); + } + } +} diff --git a/Providers/FreeSql.Provider.Odbc/GBase/__OdbcGBaseCodeFirst.cs b/Providers/FreeSql.Provider.Odbc/GBase/__OdbcGBaseCodeFirst.cs new file mode 100644 index 00000000..15478af5 --- /dev/null +++ b/Providers/FreeSql.Provider.Odbc/GBase/__OdbcGBaseCodeFirst.cs @@ -0,0 +1,387 @@ +using FreeSql.Internal; +using FreeSql.Internal.Model; +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Odbc; +using System.Linq; +using System.Text; + +namespace FreeSql.Odbc.GBase +{ + + class OdbcGBaseCodeFirst : Internal.CommonProvider.CodeFirstProvider + { + public override bool IsNoneCommandParameter { get => true; set => base.IsNoneCommandParameter = true; } + public OdbcGBaseCodeFirst(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression) : base(orm, commonUtils, commonExpression) { } + + static object _dicCsToDbLock = new object(); + static Dictionary _dicCsToDb = new Dictionary() { + + { typeof(sbyte).FullName, (OdbcType.SmallInt, "smallint","smallint NOT NULL", false, false, 0) },{ typeof(sbyte?).FullName, (OdbcType.SmallInt, "smallint", "smallint", false, true, null) }, + { typeof(short).FullName, (OdbcType.SmallInt, "smallint","smallint NOT NULL", false, false, 0) },{ typeof(short?).FullName, (OdbcType.SmallInt, "smallint", "smallint", false, true, null) }, + { typeof(int).FullName, (OdbcType.Int, "integer","integer NOT NULL", false, false, 0) },{ typeof(int?).FullName, (OdbcType.Int, "integer", "integer", false, true, null) }, + { typeof(long).FullName, (OdbcType.BigInt, "bigint","bigint NOT NULL", false, false, 0) },{ typeof(long?).FullName, (OdbcType.BigInt, "bigint", "bigint", false, true, null) }, + + { typeof(byte).FullName, (OdbcType.SmallInt, "byte","byte NOT NULL", false, false, 0) },{ typeof(byte?).FullName, (OdbcType.SmallInt, "byte", "byte", false, true, null) }, + { typeof(ushort).FullName, (OdbcType.Int, "integer","integer NOT NULL", false, false, 0) },{ typeof(ushort?).FullName, (OdbcType.Int, "integer", "integer", false, true, null) }, + { typeof(uint).FullName, (OdbcType.BigInt, "int8","int8 NOT NULL", false, false, 0) },{ typeof(uint?).FullName, (OdbcType.BigInt, "int8", "int8", false, true, null) }, + { typeof(ulong).FullName, (OdbcType.Decimal, "decimal","decimal(20,0) NOT NULL", false, false, 0) },{ typeof(ulong?).FullName, (OdbcType.Decimal, "decimal", "decimal(20,0)", false, true, null) }, + + { typeof(float).FullName, (OdbcType.Real, "smallfloat","smallfloat NOT NULL", false, false, 0) },{ typeof(float?).FullName, (OdbcType.Real, "smallfloat", "smallfloat", false, true, null) }, + { typeof(double).FullName, (OdbcType.Double, "float","float NOT NULL", false, false, 0) },{ typeof(double?).FullName, (OdbcType.Double, "float", "float", false, true, null) }, + { typeof(decimal).FullName, (OdbcType.Decimal, "decimal", "decimal(10,2) NOT NULL", false, false, 0) },{ typeof(decimal?).FullName, (OdbcType.Decimal, "decimal", "decimal(10,2)", false, true, null) }, + + { typeof(string).FullName, (OdbcType.NVarChar, "nvarchar", "nvarchar(255)", false, null, "") }, + + { typeof(TimeSpan).FullName, (OdbcType.Time, "time","time NOT NULL", false, false, 0) },{ typeof(TimeSpan?).FullName, (OdbcType.Time, "time", "time",false, true, null) }, + { typeof(DateTime).FullName, (OdbcType.DateTime, "timestamp", "timestamp NOT NULL", false, false, new DateTime(1970,1,1)) },{ typeof(DateTime?).FullName, (OdbcType.DateTime, "timestamp", "timestamp", false, true, null) }, + + { typeof(bool).FullName, (OdbcType.Bit, "char","char(1) NOT NULL", null, false, false) },{ typeof(bool?).FullName, (OdbcType.Bit, "char","char(1)", null, true, null) }, + { typeof(Byte[]).FullName, (OdbcType.VarBinary, "blob", "blob", false, null, new byte[0]) }, + + { typeof(Guid).FullName, (OdbcType.Char, "char", "char(36) NOT NULL", false, false, Guid.Empty) },{ typeof(Guid?).FullName, (OdbcType.Char, "char", "char(36)", false, true, null) }, + }; + + public override (int type, string dbtype, string dbtypeFull, bool? isnullable, object defaultValue)? GetDbInfo(Type type) + { + var isarray = type.FullName != "System.Byte[]" && type.IsArray; + var elementType = isarray ? type.GetElementType() : type; + var info = GetDbInfoNoneArray(elementType); + if (info == null) return null; + return ((int)info.Value.type, info.Value.dbtype, info.Value.dbtypeFull, info.Value.isnullable, info.Value.defaultValue); + } + (OdbcType type, string dbtype, string dbtypeFull, bool? isnullable, object defaultValue)? GetDbInfoNoneArray(Type type) + { + if (_dicCsToDb.TryGetValue(type.FullName, out var trydc)) return new (OdbcType, string, string, bool?, object)?((trydc.type, trydc.dbtype, trydc.dbtypeFull, trydc.isnullable, trydc.defaultValue)); + if (type.IsArray) return null; + var enumType = type.IsEnum ? type : null; + if (enumType == null && type.IsNullableType()) + { + var genericTypes = type.GetGenericArguments(); + if (genericTypes.Length == 1 && genericTypes.First().IsEnum) enumType = genericTypes.First(); + } + if (enumType != null) + { + var newItem = enumType.GetCustomAttributes(typeof(FlagsAttribute), false).Any() ? + (OdbcType.Int, "bigint", $"bigint{(type.IsEnum ? " NOT NULL" : "")}", false, type.IsEnum ? false : true, Enum.GetValues(enumType).GetValue(0)) : + (OdbcType.BigInt, "integer", $"integer{(type.IsEnum ? " NOT NULL" : "")}", false, type.IsEnum ? false : true, Enum.GetValues(enumType).GetValue(0)); + if (_dicCsToDb.ContainsKey(type.FullName) == false) + { + lock (_dicCsToDbLock) + { + if (_dicCsToDb.ContainsKey(type.FullName) == false) + _dicCsToDb.Add(type.FullName, newItem); + } + } + return (newItem.Item1, newItem.Item2, newItem.Item3, newItem.Item5, newItem.Item6); + } + return null; + } + + protected override string GetComparisonDDLStatements(params (Type entityType, string tableName)[] objects) + { + var sb = new StringBuilder(); + var seqcols = new List<(ColumnInfo, string[], bool)>(); //序列 + + foreach (var obj in objects) + { + if (sb.Length > 0) sb.Append("\r\n"); + var tb = _commonUtils.GetTableByEntity(obj.entityType); + if (tb == null) throw new Exception($"类型 {obj.entityType.FullName} 不可迁移"); + if (tb.Columns.Any() == false) throw new Exception($"类型 {obj.entityType.FullName} 不可迁移,可迁移属性0个"); + var tbname = tb.DbName.Split(new[] { '.' }, 2); + if (tbname?.Length == 1) tbname = new[] { "public", tbname[0] }; + + var tboldname = tb.DbOldName?.Split(new[] { '.' }, 2); //旧表名 + if (tboldname?.Length == 1) tboldname = new[] { "public", tboldname[0] }; + if (string.IsNullOrEmpty(obj.tableName) == false) + { + var tbtmpname = obj.tableName.Split(new[] { '.' }, 2); + if (tbtmpname?.Length == 1) tbtmpname = new[] { "public", tbtmpname[0] }; + if (tbname[0] != tbtmpname[0] || tbname[1] != tbtmpname[1]) + { + tbname = tbtmpname; + tboldname = null; + } + } + + if (string.Compare(tbname[0], "public", true) != 0 && _orm.Ado.ExecuteScalar(CommandType.Text, _commonUtils.FormatSql(" select 1 from pg_namespace where nspname={0}", tbname[0])) == null) //创建模式 + sb.Append("CREATE SCHEMA IF NOT EXISTS ").Append(tbname[0]).Append(";\r\n"); + + var sbalter = new StringBuilder(); + var istmpatler = false; //创建临时表,导入数据,删除旧表,修改 + if (_orm.Ado.ExecuteScalar(CommandType.Text, string.Format(" select 1 from pg_tables a inner join pg_namespace b on b.nspname = a.schemaname where b.nspname || '.' || a.tablename = '{0}.{1}'", tbname)) == null) + { //表不存在 + if (tboldname != null) + { + if (_orm.Ado.ExecuteScalar(CommandType.Text, string.Format(" select 1 from pg_tables a inner join pg_namespace b on b.nspname = a.schemaname where b.nspname || '.' || a.tablename = '{0}.{1}'", tboldname)) == null) + //旧表不存在 + tboldname = null; + } + if (tboldname == null) + { + //创建表 + var createTableName = _commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}"); + sb.Append("CREATE TABLE IF NOT EXISTS ").Append(createTableName).Append(" ( "); + foreach (var tbcol in tb.ColumnsByPosition) + { + sb.Append(" \r\n ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(" ").Append(tbcol.Attribute.DbType).Append(","); + if (tbcol.Attribute.IsIdentity == true) seqcols.Add((tbcol, tbname, true)); + } + if (tb.Primarys.Any()) + { + sb.Append(" \r\n CONSTRAINT ").Append(tbname[0]).Append("_").Append(tbname[1]).Append("_pkey PRIMARY KEY ("); + foreach (var tbcol in tb.Primarys) sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", "); + sb.Remove(sb.Length - 2, 2).Append("),"); + } + sb.Remove(sb.Length - 1, 1); + sb.Append("\r\n) WITH (OIDS=FALSE);\r\n"); + //创建表的索引 + foreach (var uk in tb.Indexes) + { + sb.Append("CREATE "); + if (uk.IsUnique) sb.Append("UNIQUE "); + sb.Append("INDEX ").Append(_commonUtils.QuoteSqlName(uk.Name)).Append(" ON ").Append(createTableName).Append("("); + foreach (var tbcol in uk.Columns) + { + sb.Append(_commonUtils.QuoteSqlName(tbcol.Column.Attribute.Name)); + if (tbcol.IsDesc) sb.Append(" DESC"); + sb.Append(", "); + } + sb.Remove(sb.Length - 2, 2).Append(");\r\n"); + } + //备注 + foreach (var tbcol in tb.ColumnsByPosition) + { + if (string.IsNullOrEmpty(tbcol.Comment) == false) + sb.Append("COMMENT ON COLUMN ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}.{tbcol.Attribute.Name}")).Append(" IS ").Append(_commonUtils.FormatSql("{0}", tbcol.Comment)).Append(";\r\n"); + } + continue; + } + //如果新表,旧表在一个数据库和模式下,直接修改表名 + if (string.Compare(tbname[0], tboldname[0], true) == 0) + sbalter.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tboldname[0]}.{tboldname[1]}")).Append(" RENAME TO ").Append(_commonUtils.QuoteSqlName($"{tbname[1]}")).Append(";\r\n"); + else + { + //如果新表,旧表不在一起,创建新表,导入数据,删除旧表 + istmpatler = true; + } + } + else + tboldname = null; //如果新表已经存在,不走改表名逻辑 + + //对比字段,只可以修改类型、增加字段、有限的修改字段名;保证安全不删除字段 + var sql = _commonUtils.FormatSql(@" +select +a.attname, +t.typname, +case when a.atttypmod > 0 and a.atttypmod < 32767 then a.atttypmod - 4 else a.attlen end len, +case when t.typelem > 0 and t.typinput::varchar = 'array_in' then t2.typname else t.typname end, +case when a.attnotnull then '0' else '1' end as is_nullable, +coalesce((select 1 from pg_sequences where sequencename = {0} || '_' || {1} || '_' || a.attname || '_sequence_name' limit 1),0) is_identity, +--e.adsrc, +a.attndims, +d.description as comment +from pg_class c +inner join pg_attribute a on a.attnum > 0 and a.attrelid = c.oid +inner join pg_type t on t.oid = a.atttypid +left join pg_type t2 on t2.oid = t.typelem +left join pg_description d on d.objoid = a.attrelid and d.objsubid = a.attnum +left join pg_attrdef e on e.adrelid = a.attrelid and e.adnum = a.attnum +inner join pg_namespace ns on ns.oid = c.relnamespace +inner join pg_namespace ns2 on ns2.oid = t.typnamespace +where ns.nspname = {0} and c.relname = {1}", tboldname ?? tbname); + var ds = _orm.Ado.ExecuteArray(CommandType.Text, sql); + var tbstruct = ds.ToDictionary(a => string.Concat(a[0]), a => + { + var attndims = int.Parse(string.Concat(a[6])); + var type = string.Concat(a[1]); + var sqlType = string.Concat(a[3]); + var max_length = long.Parse(string.Concat(a[2])); + switch (sqlType.ToLower()) + { + case "bool": case "name": case "bit": case "varbit": case "bpchar": case "varchar": case "bytea": case "text": case "uuid": break; + default: max_length *= 8; break; + } + if (type.StartsWith("_")) + { + type = type.Substring(1); + if (attndims == 0) attndims++; + } + if (sqlType.StartsWith("_")) sqlType = sqlType.Substring(1); + return new + { + column = string.Concat(a[0]), + sqlType = string.Concat(sqlType), + max_length = long.Parse(string.Concat(a[2])), + is_nullable = string.Concat(a[4]) == "1", + is_identity = string.Concat(a[5]) == "1", //string.Concat(a[5]).StartsWith(@"nextval('") && string.Concat(a[5]).EndsWith(@"'::regclass)"), + attndims, + comment = string.Concat(a[7]) + }; + }, StringComparer.CurrentCultureIgnoreCase); + + if (istmpatler == false) + { + foreach (var tbcol in tb.ColumnsByPosition) + { + if (tbstruct.TryGetValue(tbcol.Attribute.Name, out var tbstructcol) || + string.IsNullOrEmpty(tbcol.Attribute.OldName) == false && tbstruct.TryGetValue(tbcol.Attribute.OldName, out tbstructcol)) + { + var isCommentChanged = tbstructcol.comment != (tbcol.Comment ?? ""); + if (tbcol.Attribute.DbType.StartsWith(tbstructcol.sqlType, StringComparison.CurrentCultureIgnoreCase) == false || + tbcol.Attribute.DbType.Contains("[]") != (tbstructcol.attndims > 0)) + sbalter.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" ALTER COLUMN ").Append(_commonUtils.QuoteSqlName(tbstructcol.column)).Append(" TYPE ").Append(tbcol.Attribute.DbType.Split(' ').First()).Append(";\r\n"); + if (tbcol.Attribute.IsNullable != tbstructcol.is_nullable) + { + if (tbcol.Attribute.IsNullable != true || tbcol.Attribute.IsNullable == true && tbcol.Attribute.IsPrimary == false) + { + if (tbcol.Attribute.IsNullable == false) + sbalter.Append("UPDATE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" SET ").Append(_commonUtils.QuoteSqlName(tbstructcol.column)).Append(" = ").Append(tbcol.DbDefaultValue).Append(" WHERE ").Append(_commonUtils.QuoteSqlName(tbstructcol.column)).Append(" IS NULL;\r\n"); + sbalter.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" ALTER COLUMN ").Append(_commonUtils.QuoteSqlName(tbstructcol.column)).Append(" ").Append(tbcol.Attribute.IsNullable == true ? "DROP" : "SET").Append(" NOT NULL;\r\n"); + } + } + if (tbcol.Attribute.IsIdentity != tbstructcol.is_identity) + seqcols.Add((tbcol, tbname, tbcol.Attribute.IsIdentity == true)); + if (string.Compare(tbstructcol.column, tbcol.Attribute.OldName, true) == 0) + //修改列名 + sbalter.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" RENAME COLUMN ").Append(_commonUtils.QuoteSqlName(tbstructcol.column)).Append(" TO ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(";\r\n"); + if (isCommentChanged) + sbalter.Append("COMMENT ON COLUMN ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}.{tbcol.Attribute.Name}")).Append(" IS ").Append(_commonUtils.FormatSql("{0}", tbcol.Comment)).Append(";\r\n"); + continue; + } + //添加列 + sbalter.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" ADD COLUMN ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(" ").Append(tbcol.Attribute.DbType.Split(' ').First()).Append(";\r\n"); + sbalter.Append("UPDATE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" SET ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(" = ").Append(tbcol.DbDefaultValue).Append(";\r\n"); + if (tbcol.Attribute.IsNullable == false) sbalter.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" ALTER COLUMN ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(" SET NOT NULL;\r\n"); + if (tbcol.Attribute.IsIdentity == true) seqcols.Add((tbcol, tbname, tbcol.Attribute.IsIdentity == true)); + if (string.IsNullOrEmpty(tbcol.Comment) == false) sbalter.Append("COMMENT ON COLUMN ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}.{tbcol.Attribute.Name}")).Append(" IS ").Append(_commonUtils.FormatSql("{0}", tbcol.Comment)).Append(";\r\n"); + } + var dsuksql = _commonUtils.FormatSql(@" +select +c.attname, +b.relname, +case when pg_index_column_has_property(b.oid, c.attnum, 'desc') = 't' then 1 else 0 end IsDesc, +case when indisunique = 't' then 1 else 0 end IsUnique +from pg_index a +inner join pg_class b on b.oid = a.indexrelid +inner join pg_attribute c on c.attnum > 0 and c.attrelid = b.oid +inner join pg_namespace ns on ns.oid = b.relnamespace +inner join pg_class d on d.oid = a.indrelid +where ns.nspname in ({0}) and d.relname in ({1}) and a.indisprimary = 'f'", tboldname ?? tbname); + var dsuk = _orm.Ado.ExecuteArray(CommandType.Text, dsuksql).Select(a => new[] { string.Concat(a[0]), string.Concat(a[1]), string.Concat(a[2]), string.Concat(a[3]) }); + foreach (var uk in tb.Indexes) + { + if (string.IsNullOrEmpty(uk.Name) || uk.Columns.Any() == false) continue; + var dsukfind1 = dsuk.Where(a => string.Compare(a[1], uk.Name, true) == 0).ToArray(); + if (dsukfind1.Any() == false || dsukfind1.Length != uk.Columns.Length || dsukfind1.Where(a => uk.Columns.Where(b => (a[3] == "1") == uk.IsUnique && string.Compare(b.Column.Attribute.Name, a[0], true) == 0 && (a[2] == "1") == b.IsDesc).Any()).Count() != uk.Columns.Length) + { + if (dsukfind1.Any()) sbalter.Append("DROP INDEX ").Append(_commonUtils.QuoteSqlName(uk.Name)).Append(";\r\n"); + sbalter.Append("CREATE "); + if (uk.IsUnique) sbalter.Append("UNIQUE "); + sbalter.Append("INDEX ").Append(_commonUtils.QuoteSqlName(uk.Name)).Append(" ON ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append("("); + foreach (var tbcol in uk.Columns) + { + sbalter.Append(_commonUtils.QuoteSqlName(tbcol.Column.Attribute.Name)); + if (tbcol.IsDesc) sbalter.Append(" DESC"); + sbalter.Append(", "); + } + sbalter.Remove(sbalter.Length - 2, 2).Append(");\r\n"); + } + } + } + if (istmpatler == false) + { + sb.Append(sbalter); + continue; + } + var oldpk = _orm.Ado.ExecuteScalar(CommandType.Text, _commonUtils.FormatSql(@" select pg_constraint.conname as pk_name from pg_constraint +inner join pg_class on pg_constraint.conrelid = pg_class.oid +inner join pg_namespace on pg_namespace.oid = pg_class.relnamespace +where pg_namespace.nspname={0} and pg_class.relname={1} and pg_constraint.contype='p' +", tbname))?.ToString(); + if (string.IsNullOrEmpty(oldpk) == false) + sb.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" DROP CONSTRAINT ").Append(oldpk).Append(";\r\n"); + + //创建临时表,数据导进临时表,然后删除原表,将临时表改名为原表名 + var tablename = tboldname == null ? _commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}") : _commonUtils.QuoteSqlName($"{tboldname[0]}.{tboldname[1]}"); + var tmptablename = _commonUtils.QuoteSqlName($"{tbname[0]}.FreeSqlTmp_{tbname[1]}"); + //创建临时表 + sb.Append("CREATE TABLE IF NOT EXISTS ").Append(tmptablename).Append(" ( "); + foreach (var tbcol in tb.ColumnsByPosition) + { + sb.Append(" \r\n ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(" ").Append(tbcol.Attribute.DbType).Append(","); + if (tbcol.Attribute.IsIdentity == true) seqcols.Add((tbcol, tbname, true)); + } + if (tb.Primarys.Any()) + { + sb.Append(" \r\n CONSTRAINT ").Append(tbname[0]).Append("_").Append(tbname[1]).Append("_pkey PRIMARY KEY ("); + foreach (var tbcol in tb.Primarys) sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", "); + sb.Remove(sb.Length - 2, 2).Append("),"); + } + sb.Remove(sb.Length - 1, 1); + sb.Append("\r\n) WITH (OIDS=FALSE);\r\n"); + //备注 + foreach (var tbcol in tb.ColumnsByPosition) + { + if (string.IsNullOrEmpty(tbcol.Comment) == false) + sb.Append("COMMENT ON COLUMN ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.FreeSqlTmp_{tbname[1]}.{tbcol.Attribute.Name}")).Append(" IS ").Append(_commonUtils.FormatSql("{0}", tbcol.Comment)).Append(";\r\n"); + } + sb.Append("INSERT INTO ").Append(tmptablename).Append(" ("); + foreach (var tbcol in tb.ColumnsByPosition) + sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", "); + sb.Remove(sb.Length - 2, 2).Append(")\r\nSELECT "); + foreach (var tbcol in tb.ColumnsByPosition) + { + var insertvalue = "NULL"; + if (tbstruct.TryGetValue(tbcol.Attribute.Name, out var tbstructcol) || + string.IsNullOrEmpty(tbcol.Attribute.OldName) == false && tbstruct.TryGetValue(tbcol.Attribute.OldName, out tbstructcol)) + { + insertvalue = _commonUtils.QuoteSqlName(tbstructcol.column); + if (tbcol.Attribute.DbType.StartsWith(tbstructcol.sqlType, StringComparison.CurrentCultureIgnoreCase) == false) + insertvalue = $"cast({insertvalue} as {tbcol.Attribute.DbType.Split(' ').First()})"; + if (tbcol.Attribute.IsNullable != tbstructcol.is_nullable) + insertvalue = $"coalesce({insertvalue},{tbcol.DbDefaultValue})"; + } + else if (tbcol.Attribute.IsNullable == false) + insertvalue = tbcol.DbDefaultValue; + sb.Append(insertvalue).Append(", "); + } + sb.Remove(sb.Length - 2, 2).Append(" FROM ").Append(tablename).Append(";\r\n"); + sb.Append("DROP TABLE ").Append(tablename).Append(";\r\n"); + sb.Append("ALTER TABLE ").Append(tmptablename).Append(" RENAME TO ").Append(_commonUtils.QuoteSqlName(tbname[1])).Append(";\r\n"); + //创建表的索引 + foreach (var uk in tb.Indexes) + { + sb.Append("CREATE "); + if (uk.IsUnique) sb.Append("UNIQUE "); + sb.Append("INDEX ").Append(_commonUtils.QuoteSqlName(uk.Name)).Append(" ON ").Append(tablename).Append("("); + foreach (var tbcol in uk.Columns) + { + sb.Append(_commonUtils.QuoteSqlName(tbcol.Column.Attribute.Name)); + if (tbcol.IsDesc) sb.Append(" DESC"); + sb.Append(", "); + } + sb.Remove(sb.Length - 2, 2).Append(");\r\n"); + } + } + foreach (var seqcol in seqcols) + { + var tbname = seqcol.Item2; + var seqname = Utils.GetCsName($"{tbname[0]}.{tbname[1]}_{seqcol.Item1.Attribute.Name}_sequence_name"); + var tbname2 = _commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}"); + var colname2 = _commonUtils.QuoteSqlName(seqcol.Item1.Attribute.Name); + sb.Append("ALTER TABLE ").Append(tbname2).Append(" ALTER COLUMN ").Append(colname2).Append(" SET DEFAULT null;\r\n"); + sb.Append("DROP SEQUENCE IF EXISTS ").Append(seqname).Append(";\r\n"); + if (seqcol.Item3) + { + sb.Append("CREATE SEQUENCE ").Append(seqname).Append(";\r\n"); + sb.Append("ALTER TABLE ").Append(tbname2).Append(" ALTER COLUMN ").Append(colname2).Append(" SET DEFAULT nextval('").Append(seqname).Append("'::regclass);\r\n"); + sb.Append(" SELECT case when max(").Append(colname2).Append(") is null then 0 else setval('").Append(seqname).Append("', max(").Append(colname2).Append(")) end FROM ").Append(tbname2).Append(";\r\n"); + } + } + return sb.Length == 0 ? null : sb.ToString(); + } + } +} \ No newline at end of file diff --git a/Providers/FreeSql.Provider.Odbc/GBase/__OdbcGBaseDbFirst.cs b/Providers/FreeSql.Provider.Odbc/GBase/__OdbcGBaseDbFirst.cs new file mode 100644 index 00000000..e2f10f07 --- /dev/null +++ b/Providers/FreeSql.Provider.Odbc/GBase/__OdbcGBaseDbFirst.cs @@ -0,0 +1,480 @@ +using FreeSql.DatabaseModel; +using FreeSql.Internal; +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Odbc; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace FreeSql.Odbc.GBase +{ + class OdbcGBaseDbFirst : IDbFirst + { + IFreeSql _orm; + protected CommonUtils _commonUtils; + protected CommonExpression _commonExpression; + public OdbcGBaseDbFirst(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression) + { + _orm = orm; + _commonUtils = commonUtils; + _commonExpression = commonExpression; + } + + public int GetDbType(DbColumnInfo column) => (int)GetOdbcType(column); + OdbcType GetOdbcType(DbColumnInfo column) + { + var dbtype = column.DbTypeText; + var isarray = dbtype.EndsWith("[]"); + if (isarray) dbtype = dbtype.Remove(dbtype.Length - 2); + OdbcType ret = OdbcType.VarChar; + switch (dbtype.ToLower().TrimStart('_')) + { + case "int2": ret = OdbcType.SmallInt; break; + case "int4": ret = OdbcType.Int; break; + case "int8": ret = OdbcType.BigInt; break; + case "numeric": ret = OdbcType.Numeric; break; + case "float4": ret = OdbcType.Real; break; + case "float8": ret = OdbcType.Double; break; + case "money": ret = OdbcType.Numeric; break; + + case "bpchar": ret = OdbcType.Char; break; + case "varchar": ret = OdbcType.VarChar; break; + case "text": ret = OdbcType.Text; break; + + case "timestamp": ret = OdbcType.DateTime; break; + case "timestamptz": ret = OdbcType.DateTime; break; + case "date": ret = OdbcType.Date; break; + case "time": ret = OdbcType.Time; break; + case "timetz": ret = OdbcType.Time; break; + case "interval": ret = OdbcType.Time; break; + + case "bool": ret = OdbcType.Bit; break; + case "bytea": ret = OdbcType.VarBinary; break; + case "bit": ret = OdbcType.Bit; break; + case "varbit": ret = OdbcType.VarBinary; break; + + case "json": ret = OdbcType.VarChar; break; + case "jsonb": ret = OdbcType.VarChar; break; + case "uuid": ret = OdbcType.UniqueIdentifier; break; + } + return ret; + } + + static readonly Dictionary _dicDbToCs = new Dictionary() { + { (int)OdbcType.SmallInt, ("(short?)", "short.Parse({0})", "{0}.ToString()", "short?", typeof(short), typeof(short?), "{0}.Value", "GetInt16") }, + { (int)OdbcType.Int, ("(int?)", "int.Parse({0})", "{0}.ToString()", "int?", typeof(int), typeof(int?), "{0}.Value", "GetInt32") }, + { (int)OdbcType.BigInt, ("(long?)", "long.Parse({0})", "{0}.ToString()", "long?", typeof(long), typeof(long?), "{0}.Value", "GetInt64") }, + { (int)OdbcType.Numeric, ("(decimal?)", "decimal.Parse({0})", "{0}.ToString()", "decimal?", typeof(decimal), typeof(decimal?), "{0}.Value", "GetDecimal") }, + { (int)OdbcType.Real, ("(float?)", "float.Parse({0})", "{0}.ToString()", "float?", typeof(float), typeof(float?), "{0}.Value", "GetFloat") }, + { (int)OdbcType.Double, ("(double?)", "double.Parse({0})", "{0}.ToString()", "double?", typeof(double), typeof(double?), "{0}.Value", "GetDouble") }, + + { (int)OdbcType.Char, ("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", typeof(string), typeof(string), "{0}", "GetString") }, + { (int)OdbcType.VarChar, ("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", typeof(string), typeof(string), "{0}", "GetString") }, + { (int)OdbcType.Text, ("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", typeof(string), typeof(string), "{0}", "GetString") }, + + { (int)OdbcType.DateTime, ("(DateTime?)", "new DateTime(long.Parse({0}))", "{0}.Ticks.ToString()", "DateTime?", typeof(DateTime), typeof(DateTime?), "{0}.Value", "GetDateTime") }, + { (int)OdbcType.Date, ("(DateTime?)", "new DateTime(long.Parse({0}))", "{0}.Ticks.ToString()", "DateTime?", typeof(DateTime), typeof(DateTime?), "{0}.Value", "GetDateTime") }, + { (int)OdbcType.Time, ("(TimeSpan?)", "TimeSpan.Parse(double.Parse({0}))", "{0}.Ticks.ToString()", "TimeSpan?", typeof(TimeSpan), typeof(TimeSpan?), "{0}.Value", "GetValue") }, + + { (int)OdbcType.Bit, ("(bool?)", "{0} == \"1\"", "{0} == true ? \"1\" : \"0\"", "bool?", typeof(bool), typeof(bool?), "{0}.Value", "GetBoolean") }, + { (int)OdbcType.VarBinary, ("(byte[])", "Convert.FromBase64String({0})", "Convert.ToBase64String({0})", "byte[]", typeof(byte[]), typeof(byte[]), "{0}", "GetValue") }, + + { (int)OdbcType.UniqueIdentifier, ("(Guid?)", "Guid.Parse({0})", "{0}.ToString()", "Guid", typeof(Guid), typeof(Guid?), "{0}", "GetString") }, + }; + + public string GetCsConvert(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? (column.IsNullable ? trydc.csConvert : trydc.csConvert.Replace("?", "")) : null; + public string GetCsParse(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.csParse : null; + public string GetCsStringify(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.csStringify : null; + public string GetCsType(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? (column.IsNullable ? trydc.csType : trydc.csType.Replace("?", "")) : null; + public Type GetCsTypeInfo(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.csTypeInfo : null; + public string GetCsTypeValue(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.csTypeValue : null; + public string GetDataReaderMethod(DbColumnInfo column) => _dicDbToCs.TryGetValue(column.DbType, out var trydc) ? trydc.dataReaderMethod : null; + + public List GetDatabases() + { + var sql = @" select datname from pg_database where datname not in ('template1', 'template0')"; + var ds = _orm.Ado.ExecuteArray(CommandType.Text, sql); + return ds.Select(a => a.FirstOrDefault()?.ToString()).ToList(); + } + + public List GetTablesByDatabase(params string[] database) + { + var olddatabase = ""; + using (var conn = _orm.Ado.MasterPool.Get(TimeSpan.FromSeconds(5))) + { + olddatabase = conn.Value.Database; + } + var dbs = database == null || database.Any() == false ? new[] { olddatabase } : database; + var tables = new List(); + + foreach (var db in dbs) + { + if (string.IsNullOrEmpty(db) || string.Compare(db, olddatabase, true) != 0) continue; + + var loc1 = new List(); + var loc2 = new Dictionary(); + var loc3 = new Dictionary>(); + + var sql = $@" +select +b.nspname || '.' || a.tablename, +a.schemaname, +a.tablename , +d.description, +'TABLE' +from pg_tables a +inner join pg_namespace b on b.nspname = a.schemaname +inner join pg_class c on c.relnamespace = b.oid and c.relname = a.tablename +left join pg_description d on d.objoid = c.oid and objsubid = 0 +where a.schemaname not in ('pg_catalog', 'information_schema', 'topology') +and b.nspname || '.' || a.tablename not in ('public.spatial_ref_sys') + +union all + +select +b.nspname || '.' || a.relname, +b.nspname, +a.relname, +d.description, +'VIEW' +from pg_class a +inner join pg_namespace b on b.oid = a.relnamespace +left join pg_description d on d.objoid = a.oid and objsubid = 0 +where b.nspname not in ('pg_catalog', 'information_schema') and a.relkind in ('m','v') +and b.nspname || '.' || a.relname not in ('public.geography_columns','public.geometry_columns','public.raster_columns','public.raster_overviews') +"; + var ds = _orm.Ado.ExecuteArray(CommandType.Text, sql); + if (ds == null) return loc1; + + var loc6 = new List(); + var loc66 = new List(); + var loc6_1000 = new List(); + var loc66_1000 = new List(); + foreach (object[] row in ds) + { + var object_id = string.Concat(row[0]); + var owner = string.Concat(row[1]); + var table = string.Concat(row[2]); + var comment = string.Concat(row[3]); + Enum.TryParse(string.Concat(row[4]), out var type); + loc2.Add(object_id, new DbTableInfo { Id = object_id.ToString(), Schema = owner, Name = table, Comment = comment, Type = type }); + loc3.Add(object_id, new Dictionary()); + switch (type) + { + case DbTableType.VIEW: + case DbTableType.TABLE: + loc6_1000.Add(object_id); + if (loc6_1000.Count >= 500) + { + loc6.Add(loc6_1000.ToArray()); + loc6_1000.Clear(); + } + break; + case DbTableType.StoreProcedure: + loc66_1000.Add(object_id); + if (loc66_1000.Count >= 500) + { + loc66.Add(loc66_1000.ToArray()); + loc66_1000.Clear(); + } + break; + } + } + if (loc6_1000.Count > 0) loc6.Add(loc6_1000.ToArray()); + if (loc66_1000.Count > 0) loc66.Add(loc66_1000.ToArray()); + + if (loc6.Count == 0) return loc1; + var loc8 = new StringBuilder().Append("("); + for (var loc8idx = 0; loc8idx < loc6.Count; loc8idx++) + { + if (loc8idx > 0) loc8.Append(" OR "); + loc8.Append("a.table_name in ("); + for (var loc8idx2 = 0; loc8idx2 < loc6[loc8idx].Length; loc8idx2++) + { + if (loc8idx2 > 0) loc8.Append(","); + loc8.Append($"'{loc6[loc8idx][loc8idx2]}'"); + } + loc8.Append(")"); + } + loc8.Append(")"); + + sql = $@" +select +ns.nspname || '.' || c.relname as id, +a.attname, +t.typname, +case when a.atttypmod > 0 and a.atttypmod < 32767 then a.atttypmod - 4 else a.attlen end len, +case when t.typelem = 0 then t.typname else t2.typname end, +case when a.attnotnull then 0 else 1 end as is_nullable, +coalesce((select 1 from pg_sequences where sequencename = {0} || '_' || {1} || '_' || a.attname || '_sequence_name' limit 1),0) is_identity, +--e.adsrc as is_identity, +d.description as comment, +a.attndims, +case when t.typelem = 0 then t.typtype else t2.typtype end, +ns2.nspname, +a.attnum +from pg_class c +inner join pg_attribute a on a.attnum > 0 and a.attrelid = c.oid +inner join pg_type t on t.oid = a.atttypid +left join pg_type t2 on t2.oid = t.typelem +left join pg_description d on d.objoid = a.attrelid and d.objsubid = a.attnum +left join pg_attrdef e on e.adrelid = a.attrelid and e.adnum = a.attnum +inner join pg_namespace ns on ns.oid = c.relnamespace +inner join pg_namespace ns2 on ns2.oid = t.typnamespace +where {loc8.ToString().Replace("a.table_name", "ns.nspname || '.' || c.relname")}"; + ds = _orm.Ado.ExecuteArray(CommandType.Text, sql); + if (ds == null) return loc1; + + foreach (object[] row in ds) + { + var object_id = string.Concat(row[0]); + var column = string.Concat(row[1]); + var type = string.Concat(row[2]); + var max_length = int.Parse(string.Concat(row[3])); + var sqlType = string.Concat(row[4]); + var is_nullable = string.Concat(row[5]) == "1"; + var is_identity = string.Concat(row[6]) == "1"; //string.Concat(row[6]).StartsWith(@"nextval('") && string.Concat(row[6]).EndsWith(@"'::regclass)"); + var comment = string.Concat(row[7]); + int attndims = int.Parse(string.Concat(row[8])); + string typtype = string.Concat(row[9]); + string owner = string.Concat(row[10]); + int attnum = int.Parse(string.Concat(row[11])); + switch (sqlType.ToLower()) + { + case "bool": case "name": case "bit": case "varbit": case "bpchar": case "varchar": case "bytea": case "text": case "uuid": break; + default: max_length *= 8; break; + } + if (max_length <= 0) max_length = -1; + if (type.StartsWith("_")) + { + type = type.Substring(1); + if (attndims == 0) attndims++; + } + if (sqlType.StartsWith("_")) sqlType = sqlType.Substring(1); + if (max_length > 0) + { + switch (sqlType.ToLower()) + { + //case "numeric": sqlType += $"({max_length})"; break; + case "bpchar": case "varchar": case "bytea": case "bit": case "varbit": sqlType += $"({max_length})"; break; + } + } + + loc3[object_id].Add(column, new DbColumnInfo + { + Name = column, + MaxLength = max_length, + IsIdentity = is_identity, + IsNullable = is_nullable, + IsPrimary = false, + DbTypeText = type, + DbTypeTextFull = sqlType, + Table = loc2[object_id], + Coment = comment + }); + loc3[object_id][column].DbType = this.GetDbType(loc3[object_id][column]); + loc3[object_id][column].CsType = this.GetCsTypeInfo(loc3[object_id][column]); + } + + sql = $@" +select +ns.nspname || '.' || d.relname as table_id, +c.attname, +b.relname as index_id, +case when a.indisunique then 1 else 0 end IsUnique, +case when a.indisprimary then 1 else 0 end IsPrimary, +case when a.indisclustered then 0 else 1 end IsClustered, +case when pg_index_column_has_property(b.oid, c.attnum, 'desc') = 't' then 1 else 0 end IsDesc, +a.indkey::text, +c.attnum +from pg_index a +inner join pg_class b on b.oid = a.indexrelid +inner join pg_attribute c on c.attnum > 0 and c.attrelid = b.oid +inner join pg_namespace ns on ns.oid = b.relnamespace +inner join pg_class d on d.oid = a.indrelid +where {loc8.ToString().Replace("a.table_name", "ns.nspname || '.' || d.relname")} and a.indisprimary = 'f' +"; + ds = _orm.Ado.ExecuteArray(CommandType.Text, sql); + if (ds == null) return loc1; + + var indexColumns = new Dictionary>(); + var uniqueColumns = new Dictionary>(); + foreach (object[] row in ds) + { + var object_id = string.Concat(row[0]); + var column = string.Concat(row[1]); + var index_id = string.Concat(row[2]); + var is_unique = string.Concat(row[3]) == "1"; + var is_primary_key = string.Concat(row[4]) == "1"; + var is_clustered = string.Concat(row[5]) == "1"; + var is_desc = string.Concat(row[6]) == "1"; + var inkey = string.Concat(row[7]).Split(' '); + var attnum = int.Parse(string.Concat(row[8])); + attnum = int.Parse(inkey[attnum - 1]); + foreach (string tc in loc3[object_id].Keys) + { + if (loc3[object_id][tc].DbTypeText.EndsWith("[]")) + { + column = tc; + break; + } + } + if (loc3.ContainsKey(object_id) == false || loc3[object_id].ContainsKey(column) == false) continue; + var loc9 = loc3[object_id][column]; + if (loc9.IsPrimary == false && is_primary_key) loc9.IsPrimary = is_primary_key; + + Dictionary loc10 = null; + DbIndexInfo loc11 = null; + if (!indexColumns.TryGetValue(object_id, out loc10)) + indexColumns.Add(object_id, loc10 = new Dictionary()); + if (!loc10.TryGetValue(index_id, out loc11)) + loc10.Add(index_id, loc11 = new DbIndexInfo()); + loc11.Columns.Add(new DbIndexColumnInfo { Column = loc9, IsDesc = is_desc }); + if (is_unique && !is_primary_key) + { + if (!uniqueColumns.TryGetValue(object_id, out loc10)) + uniqueColumns.Add(object_id, loc10 = new Dictionary()); + if (!loc10.TryGetValue(index_id, out loc11)) + loc10.Add(index_id, loc11 = new DbIndexInfo()); + loc11.Columns.Add(new DbIndexColumnInfo { Column = loc9, IsDesc = is_desc }); + } + } + foreach (var object_id in indexColumns.Keys) + { + foreach (var column in indexColumns[object_id]) + loc2[object_id].IndexesDict.Add(column.Key, column.Value); + } + foreach (var object_id in uniqueColumns.Keys) + { + foreach (var column in uniqueColumns[object_id]) + { + column.Value.Columns.Sort((c1, c2) => c1.Column.Name.CompareTo(c2.Column.Name)); + loc2[object_id].UniquesDict.Add(column.Key, column.Value); + } + } + + sql = $@" +select +ns.nspname || '.' || b.relname as table_id, +array(select attname from pg_attribute where attrelid = a.conrelid and attnum = any(a.conkey)) as column_name, +a.conname as FKId, +ns2.nspname || '.' || c.relname as ref_table_id, +1 as IsForeignKey, +array(select attname from pg_attribute where attrelid = a.confrelid and attnum = any(a.confkey)) as ref_column, +null ref_sln, +null ref_table +from pg_constraint a +inner join pg_class b on b.oid = a.conrelid +inner join pg_class c on c.oid = a.confrelid +inner join pg_namespace ns on ns.oid = b.relnamespace +inner join pg_namespace ns2 on ns2.oid = c.relnamespace +where {loc8.ToString().Replace("a.table_name", "ns.nspname || '.' || b.relname")} +"; + ds = _orm.Ado.ExecuteArray(CommandType.Text, sql); + if (ds == null) return loc1; + + var fkColumns = new Dictionary>(); + foreach (object[] row in ds) + { + var table_id = string.Concat(row[0]); + var column = row[1] as string[]; + var fk_id = string.Concat(row[2]); + var ref_table_id = string.Concat(row[3]); + var is_foreign_key = string.Concat(row[4]) == "1"; + var referenced_column = row[5] as string[]; + var referenced_db = string.Concat(row[6]); + var referenced_table = string.Concat(row[7]); + + if (loc2.ContainsKey(ref_table_id) == false) continue; + + Dictionary loc12 = null; + DbForeignInfo loc13 = null; + if (!fkColumns.TryGetValue(table_id, out loc12)) + fkColumns.Add(table_id, loc12 = new Dictionary()); + if (!loc12.TryGetValue(fk_id, out loc13)) + loc12.Add(fk_id, loc13 = new DbForeignInfo { Table = loc2[table_id], ReferencedTable = loc2[ref_table_id] }); + + for (int a = 0; a < column.Length; a++) + { + loc13.Columns.Add(loc3[table_id][column[a]]); + loc13.ReferencedColumns.Add(loc3[ref_table_id][referenced_column[a]]); + } + } + foreach (var table_id in fkColumns.Keys) + foreach (var fk in fkColumns[table_id]) + loc2[table_id].ForeignsDict.Add(fk.Key, fk.Value); + + foreach (var table_id in loc3.Keys) + { + foreach (var loc5 in loc3[table_id].Values) + { + loc2[table_id].Columns.Add(loc5); + if (loc5.IsIdentity) loc2[table_id].Identitys.Add(loc5); + if (loc5.IsPrimary) loc2[table_id].Primarys.Add(loc5); + } + } + foreach (var loc4 in loc2.Values) + { + //if (loc4.Primarys.Count == 0 && loc4.UniquesDict.Count > 0) + //{ + // foreach (var loc5 in loc4.UniquesDict.First().Value.Columns) + // { + // loc5.Column.IsPrimary = true; + // loc4.Primarys.Add(loc5.Column); + // } + //} + loc4.Primarys.Sort((c1, c2) => c1.Name.CompareTo(c2.Name)); + loc4.Columns.Sort((c1, c2) => + { + int compare = c2.IsPrimary.CompareTo(c1.IsPrimary); + if (compare == 0) + { + bool b1 = loc4.ForeignsDict.Values.Where(fk => fk.Columns.Where(c3 => c3.Name == c1.Name).Any()).Any(); + bool b2 = loc4.ForeignsDict.Values.Where(fk => fk.Columns.Where(c3 => c3.Name == c2.Name).Any()).Any(); + compare = b2.CompareTo(b1); + } + if (compare == 0) compare = c1.Name.CompareTo(c2.Name); + return compare; + }); + loc1.Add(loc4); + } + loc1.Sort((t1, t2) => + { + var ret = t1.Schema.CompareTo(t2.Schema); + if (ret == 0) ret = t1.Name.CompareTo(t2.Name); + return ret; + }); + + loc2.Clear(); + loc3.Clear(); + tables.AddRange(loc1); + } + return tables; + } + + public List GetEnumsByDatabase(params string[] database) + { + if (database == null || database.Length == 0) return new List(); + var drs = _orm.Ado.Query<(string name, string label)>(CommandType.Text, _commonUtils.FormatSql(@" +select +ns.nspname || '.' || a.typname, +b.enumlabel +from pg_type a +inner join pg_enum b on b.enumtypid = a.oid +inner join pg_namespace ns on ns.oid = a.typnamespace +where a.typtype = 'e' and ns.nspname in (SELECT ""schema_name"" FROM information_schema.schemata where catalog_name in {0})", database)); + var ret = new Dictionary>(); + foreach (var dr in drs) + { + if (ret.TryGetValue(dr.name, out var labels) == false) ret.Add(dr.name, labels = new Dictionary()); + var key = dr.label; + if (Regex.IsMatch(key, @"^[\u0391-\uFFE5a-zA-Z_\$][\u0391-\uFFE5a-zA-Z_\$\d]*$") == false) + key = $"Unkown{ret[dr.name].Count + 1}"; + if (labels.ContainsKey(key) == false) labels.Add(key, dr.label); + } + return ret.Select(a => new DbEnumInfo { Name = a.Key, Labels = a.Value }).ToList(); + } + } +} \ No newline at end of file diff --git a/Providers/FreeSql.Provider.Odbc/GBase/__OdbcGBaseExpression.cs b/Providers/FreeSql.Provider.Odbc/GBase/__OdbcGBaseExpression.cs new file mode 100644 index 00000000..c250c87f --- /dev/null +++ b/Providers/FreeSql.Provider.Odbc/GBase/__OdbcGBaseExpression.cs @@ -0,0 +1,554 @@ +using FreeSql.Internal; +using FreeSql.Internal.Model; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; + +namespace FreeSql.Odbc.GBase +{ + class OdbcGBaseExpression : CommonExpression + { + + public OdbcGBaseExpression(CommonUtils common) : base(common) { } + + public override string ExpressionLambdaToSqlOther(Expression exp, ExpTSC tsc) + { + Func getExp = exparg => ExpressionLambdaToSql(exparg, tsc); + switch (exp.NodeType) + { + case ExpressionType.Convert: + var operandExp = (exp as UnaryExpression)?.Operand; + var gentype = exp.Type.NullableTypeOrThis(); + if (gentype != operandExp.Type.NullableTypeOrThis()) + { + 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)"; + case "System.DateTime": return $"({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)})::varchar"; + 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"; + } + } + break; + case ExpressionType.ArrayLength: + var arrOperExp = getExp((exp as UnaryExpression).Operand); + if (arrOperExp.StartsWith("(") || arrOperExp.EndsWith(")")) return $"array_length(array[{arrOperExp.TrimStart('(').TrimEnd(')')}],1)"; + return $"case when {arrOperExp} is null then 0 else array_length({arrOperExp},1) end"; + case ExpressionType.Call: + var callExp = exp as MethodCallExpression; + + switch (callExp.Method.Name) + { + case "Parse": + case "TryParse": + 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)"; + case "System.DateTime": return $"({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"; + } + break; + case "NewGuid": + return null; + case "Next": + if (callExp.Object?.Type == typeof(Random)) return "(random()*1000000000)::int4"; + return null; + case "NextDouble": + if (callExp.Object?.Type == typeof(Random)) return "random()"; + return null; + case "Random": + if (callExp.Method.DeclaringType.IsNumberType()) return "random()"; + return null; + case "ToString": + if (callExp.Object != null) return callExp.Arguments.Count == 0 ? $"({getExp(callExp.Object)})::varchar" : null; + return null; + } + + var objExp = callExp.Object; + var objType = objExp?.Type; + if (objType?.FullName == "System.Byte[]") return null; + + var argIndex = 0; + if (objType == null && callExp.Method.DeclaringType == typeof(Enumerable)) + { + objExp = callExp.Arguments.FirstOrDefault(); + objType = objExp?.Type; + argIndex++; + } + if (objType == null) objType = callExp.Method.DeclaringType; + if (objType != null || objType.IsArrayOrList()) + { + string left = null; + if (objType.FullName == typeof(Dictionary).FullName) + { + left = objExp == null ? null : getExp(objExp); + switch (callExp.Method.Name) + { + 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})"; + } + } + 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 = tsc.SetDbParamsReturnOld(null); + left = objExp == null ? null : getExp(objExp); + tsc.SetMapColumnTmp(null).SetMapTypeReturnOld(oldMapType); + 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.HasValue) args1 = $"{args1}::{dbinfo.Value.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"; + } + } + 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.FullName == typeof(Dictionary).FullName) + { + 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["); + for (var a = 0; a < arrExp.Expressions.Count; a++) + { + if (a > 0) arrSb.Append(","); + arrSb.Append(getExp(arrExp.Expressions[a])); + } + if (arrSb.Length == 1) arrSb.Append("NULL"); + return arrSb.Append("]").ToString(); + case ExpressionType.ListInit: + var listExp = exp as ListInitExpression; + var listSb = new StringBuilder(); + listSb.Append("("); + for (var a = 0; a < listExp.Initializers.Count; a++) + { + if (listExp.Initializers[a].Arguments.Any() == false) continue; + 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: + var newExp = exp as NewExpression; + if (typeof(IList).IsAssignableFrom(newExp.Type)) + { + if (newExp.Arguments.Count == 0) return "(NULL)"; + if (typeof(IEnumerable).IsAssignableFrom(newExp.Arguments[0].Type) == false) return "(NULL)"; + return getExp(newExp.Arguments[0]); + } + return null; + } + return null; + } + + public override string ExpressionLambdaToSqlMemberAccessString(MemberExpression exp, ExpTSC tsc) + { + if (exp.Expression == null) + { + switch (exp.Member.Name) + { + case "Empty": return "''"; + } + return null; + } + var left = ExpressionLambdaToSql(exp.Expression, tsc); + switch (exp.Member.Name) + { + case "Length": return $"char_length({left})"; + } + return null; + } + public override string ExpressionLambdaToSqlMemberAccessDateTime(MemberExpression exp, ExpTSC tsc) + { + if (exp.Expression == null) + { + switch (exp.Member.Name) + { + case "Now": return _common.Now; + case "UtcNow": return _common.NowUtc; + case "Today": return "current_date"; + case "MinValue": return "'0001/1/1 0:00:00'::timestamp"; + case "MaxValue": return "'9999/12/31 23:59:59'::timestamp"; + } + 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)"; + } + return null; + } + public override string ExpressionLambdaToSqlMemberAccessTimeSpan(MemberExpression exp, ExpTSC tsc) + { + if (exp.Expression == null) + { + switch (exp.Member.Name) + { + case "Zero": return "0"; + case "MinValue": return "-922337203685477580"; //微秒 Ticks / 10 + case "MaxValue": return "922337203685477580"; + } + return null; + } + 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)"; + } + return null; + } + + public override string ExpressionLambdaToSqlCallString(MethodCallExpression exp, ExpTSC tsc) + { + Func getExp = exparg => ExpressionLambdaToSql(exparg, tsc); + if (exp.Object == null) + { + switch (exp.Method.Name) + { + case "IsNullOrEmpty": + var arg1 = getExp(exp.Arguments[0]); + return $"({arg1} is null or {arg1} = '')"; + case "IsNullOrWhiteSpace": + var arg2 = getExp(exp.Arguments[0]); + return $"({arg2} is null or {arg2} = '' or ltrim({arg2}) = '')"; + case "Concat": + return _common.StringConcat(exp.Arguments.Select(a => getExp(a)).ToArray(), null); + } + } + else + { + var left = getExp(exp.Object); + switch (exp.Method.Name) + { + case "StartsWith": + case "EndsWith": + case "Contains": + var args0Value = getExp(exp.Arguments[0]); + if (args0Value == "NULL") return $"({left}) IS NULL"; + var likeOpt = "LIKE"; + if (exp.Arguments.Count > 1) + { + if (exp.Arguments[1].Type == typeof(bool) || + exp.Arguments[1].Type == typeof(StringComparison) && getExp(exp.Arguments[0]).Contains("IgnoreCase")) likeOpt = "ILIKE"; + } + if (exp.Method.Name == "StartsWith") return $"({left}) {likeOpt} {(args0Value.EndsWith("'") ? args0Value.Insert(args0Value.Length - 1, "%") : $"(({args0Value})::varchar || '%')")}"; + if (exp.Method.Name == "EndsWith") return $"({left}) {likeOpt} {(args0Value.StartsWith("'") ? args0Value.Insert(1, "%") : $"('%' || ({args0Value})::varchar)")}"; + if (args0Value.StartsWith("'") && args0Value.EndsWith("'")) return $"({left}) {likeOpt} {args0Value.Insert(1, "%").Insert(args0Value.Length, "%")}"; + return $"({left}) {likeOpt} ('%' || ({args0Value})::varchar || '%')"; + 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])})"; + case "IndexOf": return $"(strpos({left}, {getExp(exp.Arguments[0])})-1)"; + case "PadLeft": + if (exp.Arguments.Count == 1) return $"lpad({left}, {getExp(exp.Arguments[0])})"; + return $"lpad({left}, {getExp(exp.Arguments[0])}, {getExp(exp.Arguments[1])})"; + case "PadRight": + if (exp.Arguments.Count == 1) return $"rpad({left}, {getExp(exp.Arguments[0])})"; + return $"rpad({left}, {getExp(exp.Arguments[0])}, {getExp(exp.Arguments[1])})"; + case "Trim": + case "TrimStart": + case "TrimEnd": + if (exp.Arguments.Count == 0) + { + if (exp.Method.Name == "Trim") return $"trim({left})"; + if (exp.Method.Name == "TrimStart") return $"ltrim({left})"; + if (exp.Method.Name == "TrimEnd") return $"rtrim({left})"; + } + var trimArg1 = ""; + var trimArg2 = ""; + foreach (var argsTrim02 in exp.Arguments) + { + var argsTrim01s = new[] { argsTrim02 }; + if (argsTrim02.NodeType == ExpressionType.NewArrayInit) + { + var arritem = argsTrim02 as NewArrayExpression; + argsTrim01s = arritem.Expressions.ToArray(); + } + foreach (var argsTrim01 in argsTrim01s) + { + var trimChr = getExp(argsTrim01).Trim('\''); + if (trimChr.Length == 1) trimArg1 += trimChr; + else trimArg2 += $" || ({trimChr})"; + } + } + if (exp.Method.Name == "Trim") left = $"trim({left}, {_common.FormatSql("{0}", trimArg1)}{trimArg2})"; + if (exp.Method.Name == "TrimStart") left = $"ltrim({left}, {_common.FormatSql("{0}", trimArg1)}{trimArg2})"; + if (exp.Method.Name == "TrimEnd") 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])})::varchar)"; + } + } + return null; + } + public override string ExpressionLambdaToSqlCallMath(MethodCallExpression exp, ExpTSC tsc) + { + Func getExp = exparg => ExpressionLambdaToSql(exparg, tsc); + switch (exp.Method.Name) + { + case "Abs": return $"abs({getExp(exp.Arguments[0])})"; + case "Sign": return $"sign({getExp(exp.Arguments[0])})"; + case "Floor": return $"floor({getExp(exp.Arguments[0])})"; + case "Ceiling": return $"ceiling({getExp(exp.Arguments[0])})"; + case "Round": + if (exp.Arguments.Count > 1 && exp.Arguments[1].Type.FullName == "System.Int32") return $"round({getExp(exp.Arguments[0])}, {getExp(exp.Arguments[1])})"; + return $"round({getExp(exp.Arguments[0])})"; + case "Exp": return $"exp({getExp(exp.Arguments[0])})"; + case "Log": return $"log({getExp(exp.Arguments[0])})"; + case "Log10": return $"log10({getExp(exp.Arguments[0])})"; + case "Pow": return $"pow({getExp(exp.Arguments[0])}, {getExp(exp.Arguments[1])})"; + case "Sqrt": return $"sqrt({getExp(exp.Arguments[0])})"; + case "Cos": return $"cos({getExp(exp.Arguments[0])})"; + case "Sin": return $"sin({getExp(exp.Arguments[0])})"; + case "Tan": return $"tan({getExp(exp.Arguments[0])})"; + case "Acos": return $"acos({getExp(exp.Arguments[0])})"; + case "Asin": return $"asin({getExp(exp.Arguments[0])})"; + case "Atan": return $"atan({getExp(exp.Arguments[0])})"; + case "Atan2": return $"atan2({getExp(exp.Arguments[0])}, {getExp(exp.Arguments[1])})"; + case "Truncate": return $"trunc({getExp(exp.Arguments[0])}, 0)"; + } + return null; + } + public override string ExpressionLambdaToSqlCallDateTime(MethodCallExpression exp, ExpTSC tsc) + { + Func getExp = exparg => ExpressionLambdaToSql(exparg, tsc); + if (exp.Object == null) + { + 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 $"({getExp(exp.Arguments[0])})::timestamp"; + case "ParseExact": + case "TryParse": + case "TryParseExact": return $"({getExp(exp.Arguments[0])})::timestamp"; + } + } + else + { + var left = getExp(exp.Object); + var args1 = exp.Arguments.Count == 0 ? null : getExp(exp.Arguments[0]); + switch (exp.Method.Name) + { + case "Add": return $"(({left})::timestamp+(({args1})||' microseconds')::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 "Subtract": + 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})||' microseconds')::interval)"; + } + break; + case "Equals": return $"({left} = ({args1})::timestamp)"; + case "CompareTo": return $"extract(epoch from ({left})::timestamp-({args1})::timestamp)"; + case "ToString": return exp.Arguments.Count == 0 ? $"to_char({left}, 'YYYY-MM-DD HH24:MI:SS.US')" : null; + } + } + return null; + } + public override string ExpressionLambdaToSqlCallTimeSpan(MethodCallExpression exp, ExpTSC tsc) + { + Func getExp = exparg => ExpressionLambdaToSql(exparg, tsc); + if (exp.Object == null) + { + switch (exp.Method.Name) + { + 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 "ParseExact": + case "TryParse": + case "TryParseExact": return $"({getExp(exp.Arguments[0])})::int8"; + } + } + else + { + var left = getExp(exp.Object); + var args1 = exp.Arguments.Count == 0 ? null : getExp(exp.Arguments[0]); + switch (exp.Method.Name) + { + case "Add": return $"({left}+{args1})"; + case "Subtract": return $"({left}-({args1}))"; + case "Equals": return $"({left} = {args1})"; + case "CompareTo": return $"({left}-({args1}))"; + case "ToString": return $"({left})::varchar"; + } + } + return null; + } + public override string ExpressionLambdaToSqlCallConvert(MethodCallExpression exp, ExpTSC tsc) + { + Func getExp = exparg => ExpressionLambdaToSql(exparg, tsc); + if (exp.Object == null) + { + 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 "ToDateTime": return $"({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])})::varchar"; + 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 null; + } + } +} diff --git a/Providers/FreeSql.Provider.Odbc/GBase/__OdbcGBaseUtils.cs b/Providers/FreeSql.Provider.Odbc/GBase/__OdbcGBaseUtils.cs new file mode 100644 index 00000000..ee39daff --- /dev/null +++ b/Providers/FreeSql.Provider.Odbc/GBase/__OdbcGBaseUtils.cs @@ -0,0 +1,167 @@ +using FreeSql.Internal; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Data.Common; +using System.Linq; +using System.Net; +using System.Text; +using System.Linq.Expressions; +using System.Reflection; +using System.Data.Odbc; +using FreeSql.Internal.Model; + +namespace FreeSql.Odbc.GBase +{ + + class OdbcGBaseUtils : CommonUtils + { + public OdbcGBaseUtils(IFreeSql orm) : base(orm) + { + } + + static Array getParamterArrayValue(Type arrayType, object value, object defaultValue) + { + var valueArr = value as Array; + var len = valueArr.GetLength(0); + var ret = Array.CreateInstance(arrayType, len); + for (var a = 0; a < len; a++) + { + var item = valueArr.GetValue(a); + ret.SetValue(item == null ? defaultValue : getParamterValue(item.GetType(), item, 1), a); + } + return ret; + } + static Dictionary> dicGetParamterValue = new Dictionary> { + { typeof(uint).FullName, a => long.Parse(string.Concat(a)) }, { typeof(uint[]).FullName, a => getParamterArrayValue(typeof(long), a, 0) }, { typeof(uint?[]).FullName, a => getParamterArrayValue(typeof(long?), a, null) }, + { typeof(ulong).FullName, a => decimal.Parse(string.Concat(a)) }, { typeof(ulong[]).FullName, a => getParamterArrayValue(typeof(decimal), a, 0) }, { typeof(ulong?[]).FullName, a => getParamterArrayValue(typeof(decimal?), a, null) }, + { typeof(ushort).FullName, a => int.Parse(string.Concat(a)) }, { typeof(ushort[]).FullName, a => getParamterArrayValue(typeof(int), a, 0) }, { typeof(ushort?[]).FullName, a => getParamterArrayValue(typeof(int?), a, null) }, + { typeof(byte).FullName, a => short.Parse(string.Concat(a)) }, { typeof(byte[]).FullName, a => getParamterArrayValue(typeof(short), a, 0) }, { typeof(byte?[]).FullName, a => getParamterArrayValue(typeof(short?), a, null) }, + { typeof(sbyte).FullName, a => short.Parse(string.Concat(a)) }, { typeof(sbyte[]).FullName, a => getParamterArrayValue(typeof(short), a, 0) }, { typeof(sbyte?[]).FullName, a => getParamterArrayValue(typeof(short?), a, null) }, + }; + static object getParamterValue(Type type, object value, int level = 0) + { + if (type.FullName == "System.Byte[]") return value; + if (type.IsArray && level == 0) + { + var elementType = type.GetElementType(); + Type enumType = null; + if (elementType.IsEnum) enumType = elementType; + else if (elementType.IsNullableType()) + { + var genericTypesFirst = elementType.GetGenericArguments().First(); + if (genericTypesFirst.IsEnum) enumType = genericTypesFirst; + } + if (enumType != null) return enumType.GetCustomAttributes(typeof(FlagsAttribute), false).Any() ? + getParamterArrayValue(typeof(long), value, elementType.IsEnum ? null : Enum.GetValues(enumType).GetValue(0)) : + getParamterArrayValue(typeof(int), value, elementType.IsEnum ? null : Enum.GetValues(enumType).GetValue(0)); + return dicGetParamterValue.TryGetValue(type.FullName, out var trydicarr) ? trydicarr(value) : value; + } + if (type.IsNullableType()) type = type.GetGenericArguments().First(); + if (type.IsEnum) return (int)value; + if (dicGetParamterValue.TryGetValue(type.FullName, out var trydic)) return trydic(value); + return value; + } + + public override DbParameter AppendParamter(List _params, string parameterName, ColumnInfo col, Type type, object value) + { + if (string.IsNullOrEmpty(parameterName)) parameterName = $"p_{_params?.Count}"; + if (value != null) value = getParamterValue(type, value); + var ret = new OdbcParameter { ParameterName = QuoteParamterName(parameterName), Value = value }; + //if (value.GetType().IsEnum || value.GetType().GenericTypeArguments.FirstOrDefault()?.IsEnum == true) { + // ret.DataTypeName = ""; + //} else { + var tp = _orm.CodeFirst.GetDbInfo(type)?.type; + if (tp != null) ret.OdbcType = (OdbcType)tp.Value; + //} + _params?.Add(ret); + return ret; + } + + public override DbParameter[] GetDbParamtersByObject(string sql, object obj) => + Utils.GetDbParamtersByObject(sql, obj, null, (name, type, value) => + { + if (value != null) value = getParamterValue(type, value); + var ret = new OdbcParameter { ParameterName = $"@{name}", Value = value }; + //if (value.GetType().IsEnum || value.GetType().GenericTypeArguments.FirstOrDefault()?.IsEnum == true) { + // ret.DataTypeName = ""; + //} else { + var tp = _orm.CodeFirst.GetDbInfo(type)?.type; + if (tp != null) ret.OdbcType = (OdbcType)tp.Value; + //} + return ret; + }); + + public override string FormatSql(string sql, params object[] args) => sql?.FormatOdbcPostgreSQL(args); + public override string QuoteSqlName(string name) + { + var nametrim = name.Trim(); + if (nametrim.StartsWith("(") && nametrim.EndsWith(")")) + return nametrim; //原生SQL + return $"\"{nametrim.Trim('"').Replace(".", "\".\"")}\""; + } + public override string TrimQuoteSqlName(string name) + { + var nametrim = name.Trim(); + if (nametrim.StartsWith("(") && nametrim.EndsWith(")")) + return nametrim; //原生SQL + return $"{nametrim.Trim('"').Replace("\".\"", ".").Replace(".\"", ".")}"; + } + public override string QuoteParamterName(string name) => $"@{(_orm.CodeFirst.IsSyncStructureToLower ? name.ToLower() : name)}"; + public override string IsNull(string sql, object value) => $"coalesce({sql}, {value})"; + 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 => "current_timestamp"; + public override string NowUtc => "(current_timestamp at time zone 'UTC')"; + + public override string QuoteWriteParamter(Type type, string paramterName) => paramterName; + public override string QuoteReadColumn(Type type, string columnName) => columnName; + + public override string GetNoneParamaterSqlValue(List specialParams, Type type, object value) + { + if (value == null) return "NULL"; + value = getParamterValue(type, value); + var type2 = value.GetType(); + if (type2 == typeof(byte[])) + { + var bytes = value as byte[]; + var sb = new StringBuilder().Append("'\\x"); + foreach (var vc in bytes) + { + if (vc < 10) sb.Append("0"); + sb.Append(vc.ToString("X")); + } + return sb.Append("'").ToString(); //val = Encoding.UTF8.GetString(val as byte[]); + } + else 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, eleType, item)); + } + sb.Append("]"); + var dbinfo = _orm.CodeFirst.GetDbInfo(type); + if (dbinfo.HasValue) sb.Append("::").Append(dbinfo.Value.dbtype); + return sb.ToString(); + } + else if (dicGetParamterValue.ContainsKey(type2.FullName)) + { + value = string.Concat(value); + } + return FormatSql("{0}", value, 1); + } + } +} diff --git a/Providers/FreeSql.Provider.Odbc/Oracle/OdbcOracleCodeFirst.cs b/Providers/FreeSql.Provider.Odbc/Oracle/OdbcOracleCodeFirst.cs index d39e4c48..ad61cdb9 100644 --- a/Providers/FreeSql.Provider.Odbc/Oracle/OdbcOracleCodeFirst.cs +++ b/Providers/FreeSql.Provider.Odbc/Oracle/OdbcOracleCodeFirst.cs @@ -182,7 +182,7 @@ a.data_length, a.data_precision, a.data_scale, a.char_used, -case when a.nullable = 'Y' then 1 else 0 end, +case when a.nullable = 'N' then 0 else 1 end, nvl((select 1 from user_sequences where sequence_name='{Utils.GetCsName((tboldname ?? tbname).Last())}_seq_'||a.column_name), 0), nvl((select 1 from user_triggers where trigger_name='{Utils.GetCsName((tboldname ?? tbname).Last())}_seq_'||a.column_name||'TI'), 0), b.comments diff --git a/Providers/FreeSql.Provider.Odbc/Oracle/OdbcOracleDbFirst.cs b/Providers/FreeSql.Provider.Odbc/Oracle/OdbcOracleDbFirst.cs index fe87f450..dfcbfa6b 100644 --- a/Providers/FreeSql.Provider.Odbc/Oracle/OdbcOracleDbFirst.cs +++ b/Providers/FreeSql.Provider.Odbc/Oracle/OdbcOracleDbFirst.cs @@ -258,7 +258,7 @@ a.data_length, a.data_precision, a.data_scale, a.char_used, -case when a.nullable = 'Y' then 1 else 0 end, +case when a.nullable = 'N' then 0 else 1 end, nvl((select 1 from user_sequences where upper(sequence_name)=upper(a.table_name||'_seq_'||a.column_name)), 0), b.comments from all_tab_cols a diff --git a/Providers/FreeSql.Provider.Odbc/PostgreSQL/OdbcPostgreSQLAdo/OdbcPostgreSQLConnectionPool.cs b/Providers/FreeSql.Provider.Odbc/PostgreSQL/OdbcPostgreSQLAdo/OdbcPostgreSQLConnectionPool.cs index dfc21556..69688f14 100644 --- a/Providers/FreeSql.Provider.Odbc/PostgreSQL/OdbcPostgreSQLAdo/OdbcPostgreSQLConnectionPool.cs +++ b/Providers/FreeSql.Provider.Odbc/PostgreSQL/OdbcPostgreSQLAdo/OdbcPostgreSQLConnectionPool.cs @@ -57,7 +57,7 @@ namespace FreeSql.Odbc.PostgreSQL { internal OdbcPostgreSQLConnectionPool _pool; - public string Name { get; set; } = "PostgreSQL NpgsqlConnection 对象池"; + public string Name { get; set; } = "PostgreSQL OdbcConnection 对象池"; public int PoolSize { get; set; } = 50; public TimeSpan SyncGetTimeout { get; set; } = TimeSpan.FromSeconds(10); public TimeSpan IdleTimeout { get; set; } = TimeSpan.Zero; diff --git a/Providers/FreeSql.Provider.Oracle/OracleCodeFirst.cs b/Providers/FreeSql.Provider.Oracle/OracleCodeFirst.cs index 7fdff83f..e408452f 100644 --- a/Providers/FreeSql.Provider.Oracle/OracleCodeFirst.cs +++ b/Providers/FreeSql.Provider.Oracle/OracleCodeFirst.cs @@ -183,7 +183,7 @@ a.data_length, a.data_precision, a.data_scale, a.char_used, -case when a.nullable = 'Y' then 1 else 0 end, +case when a.nullable = 'N' then 0 else 1 end, nvl((select 1 from user_sequences where sequence_name='{Utils.GetCsName((tboldname ?? tbname).Last())}_seq_'||a.column_name), 0), nvl((select 1 from user_triggers where trigger_name='{Utils.GetCsName((tboldname ?? tbname).Last())}_seq_'||a.column_name||'TI'), 0), b.comments diff --git a/Providers/FreeSql.Provider.Oracle/OracleDbFirst.cs b/Providers/FreeSql.Provider.Oracle/OracleDbFirst.cs index 089e2791..8f24499e 100644 --- a/Providers/FreeSql.Provider.Oracle/OracleDbFirst.cs +++ b/Providers/FreeSql.Provider.Oracle/OracleDbFirst.cs @@ -258,7 +258,7 @@ a.data_length, a.data_precision, a.data_scale, a.char_used, -case when a.nullable = 'Y' then 1 else 0 end, +case when a.nullable = 'N' then 0 else 1 end, nvl((select 1 from user_sequences where upper(sequence_name)=upper(a.table_name||'_seq_'||a.column_name)), 0), b.comments from all_tab_cols a