diff --git a/Providers/FreeSql.Provider.QuestDb/Curd/OnConflictDoUpdate.cs b/Providers/FreeSql.Provider.QuestDb/Curd/OnConflictDoUpdate.cs new file mode 100644 index 00000000..ee704784 --- /dev/null +++ b/Providers/FreeSql.Provider.QuestDb/Curd/OnConflictDoUpdate.cs @@ -0,0 +1,217 @@ +using FreeSql.Aop; +using FreeSql.Internal.Model; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace FreeSql.QuestDb.Curd +{ + public class OnConflictDoUpdate where T1 : class + { + internal QuestDbInsert _pgsqlInsert; + internal QuestDbUpdate _pgsqlUpdatePriv; + internal QuestDbUpdate _pgsqlUpdate => _pgsqlUpdatePriv ?? + (_pgsqlUpdatePriv = new QuestDbUpdate(_pgsqlInsert.InternalOrm, _pgsqlInsert.InternalCommonUtils, _pgsqlInsert.InternalCommonExpression, null) { InternalTableAlias = "EXCLUDED" } + .NoneParameter().SetSource(_pgsqlInsert.InternalSource) as QuestDbUpdate); + internal ColumnInfo[] _tempPrimarys; + bool _doNothing; + + public OnConflictDoUpdate(IInsert insert, Expression> columns = null) + { + _pgsqlInsert = insert as QuestDbInsert; + if (_pgsqlInsert == null) throw new Exception(CoreStrings.S_Features_Unique("OnConflictDoUpdate", "PostgreSQL")); + if (_pgsqlInsert._noneParameterFlag == "c") _pgsqlInsert._noneParameterFlag = "cu"; + + if (columns != null) + { + var colsList = new List(); + var cols = _pgsqlInsert.InternalCommonExpression.ExpressionSelectColumns_MemberAccess_New_NewArrayInit(null, null, columns?.Body, false, null).ToDictionary(a => a, a => true); + foreach (var col in _pgsqlInsert.InternalTable.Columns.Values) + if (cols.ContainsKey(col.Attribute.Name)) + colsList.Add(col); + _tempPrimarys = colsList.ToArray(); + } + if (_tempPrimarys == null || _tempPrimarys.Any() == false) + _tempPrimarys = _pgsqlInsert.InternalTable.Primarys; + if (_tempPrimarys.Any() == false) throw new Exception(CoreStrings.S_OnConflictDoUpdate_MustIsPrimary); + } + + protected void ClearData() + { + _pgsqlInsert.InternalClearData(); + _pgsqlUpdatePriv = null; + } + + public OnConflictDoUpdate IgnoreColumns(Expression> columns) + { + _pgsqlUpdate.IgnoreColumns(columns); + return this; + } + public OnConflictDoUpdate UpdateColumns(Expression> columns) + { + _pgsqlUpdate.UpdateColumns(columns); + return this; + } + public OnConflictDoUpdate IgnoreColumns(string[] columns) + { + _pgsqlUpdate.IgnoreColumns(columns); + return this; + } + public OnConflictDoUpdate UpdateColumns(string[] columns) + { + _pgsqlUpdate.UpdateColumns(columns); + return this; + } + + public OnConflictDoUpdate Set(Expression> column, TMember value) + { + _pgsqlUpdate.Set(column, value); + return this; + } + //由于表达式解析问题,ON CONFLICT("id") DO UPDATE SET 需要指定表别名,如 Set(a => a.Clicks + 1) 解析会失败 + //暂时不开放这个功能,如有需要使用 SetRaw("click = t.click + 1") 替代该操作 + //public OnConflictDoUpdate Set(Expression> exp) + //{ + // _pgsqlUpdate.Set(exp); + // return this; + //} + public OnConflictDoUpdate SetRaw(string sql) + { + _pgsqlUpdate.SetRaw(sql); + return this; + } + + public OnConflictDoUpdate DoNothing() + { + _doNothing = true; + return this; + } + + public string ToSql() + { + var sb = new StringBuilder(); + sb.Append(_pgsqlInsert.ToSql()).Append("\r\nON CONFLICT("); + for (var a = 0; a < _tempPrimarys.Length; a++) + { + if (a > 0) sb.Append(", "); + sb.Append(_pgsqlInsert.InternalCommonUtils.QuoteSqlName(_tempPrimarys[a].Attribute.Name)); + } + if (_doNothing) + { + sb.Append(") DO NOTHING"); + } + else + { + sb.Append(") DO UPDATE SET\r\n"); + + if (_pgsqlUpdate._tempPrimarys.Any() == false) _pgsqlUpdate._tempPrimarys = _tempPrimarys; + var sbSetEmpty = _pgsqlUpdate.InternalSbSet.Length == 0; + var sbSetIncrEmpty = _pgsqlUpdate.InternalSbSetIncr.Length == 0; + if (sbSetEmpty == false || sbSetIncrEmpty == false) + { + if (sbSetEmpty == false) sb.Append(_pgsqlUpdate.InternalSbSet.ToString().Substring(2)); + if (sbSetIncrEmpty == false) sb.Append(sbSetEmpty ? _pgsqlUpdate.InternalSbSetIncr.ToString().Substring(2) : _pgsqlUpdate.InternalSbSetIncr.ToString()); + } + else + { + var colidx = 0; + foreach (var col in _pgsqlInsert.InternalTable.Columns.Values) + { + if (col.Attribute.IsPrimary || _pgsqlUpdate.InternalIgnore.ContainsKey(col.Attribute.Name)) continue; + + if (colidx > 0) sb.Append(", \r\n"); + + if (col.Attribute.IsVersion == true && col.Attribute.MapType != typeof(byte[])) + { + var field = _pgsqlInsert.InternalCommonUtils.QuoteSqlName(col.Attribute.Name); + sb.Append(field).Append(" = ").Append(_pgsqlInsert.InternalCommonUtils.QuoteSqlName(_pgsqlInsert.InternalTable.DbName)).Append(".").Append(field).Append(" + 1"); + } + else if (_pgsqlInsert.InternalIgnore.ContainsKey(col.Attribute.Name)) + { + if (string.IsNullOrEmpty(col.DbUpdateValue) == false) + { + sb.Append(_pgsqlInsert.InternalCommonUtils.QuoteSqlName(col.Attribute.Name)).Append(" = ").Append(col.DbUpdateValue); + } + else + { + var caseWhen = _pgsqlUpdate.InternalWhereCaseSource(col.CsName, sqlval => sqlval).Trim(); + sb.Append(caseWhen); + if (caseWhen.EndsWith(" END")) _pgsqlUpdate.InternalToSqlCaseWhenEnd(sb, col); + } + } + else + { + var field = _pgsqlInsert.InternalCommonUtils.QuoteSqlName(col.Attribute.Name); + sb.Append(field).Append(" = EXCLUDED.").Append(field); + } + ++colidx; + } + } + } + + return sb.ToString(); + } + + public long ExecuteAffrows() + { + var sql = this.ToSql(); + if (string.IsNullOrEmpty(sql)) return 0; + + var before = new CurdBeforeEventArgs(_pgsqlInsert.InternalTable.Type, _pgsqlInsert.InternalTable, CurdType.Insert, sql, _pgsqlInsert.InternalParams); + _pgsqlInsert.InternalOrm.Aop.CurdBeforeHandler?.Invoke(_pgsqlInsert, before); + long ret = 0; + Exception exception = null; + try + { + ret = _pgsqlInsert.InternalOrm.Ado.ExecuteNonQuery(_pgsqlInsert.InternalConnection, _pgsqlInsert.InternalTransaction, CommandType.Text, sql, _pgsqlInsert._commandTimeout, _pgsqlInsert.InternalParams); + } + catch (Exception ex) + { + exception = ex; + throw ex; + } + finally + { + var after = new CurdAfterEventArgs(before, exception, ret); + _pgsqlInsert.InternalOrm.Aop.CurdAfterHandler?.Invoke(_pgsqlInsert, after); + ClearData(); + } + return ret; + } + +#if net40 +#else + async public Task ExecuteAffrowsAsync(CancellationToken cancellationToken = default) + { + var sql = this.ToSql(); + if (string.IsNullOrEmpty(sql)) return 0; + + var before = new CurdBeforeEventArgs(_pgsqlInsert.InternalTable.Type, _pgsqlInsert.InternalTable, CurdType.Insert, sql, _pgsqlInsert.InternalParams); + _pgsqlInsert.InternalOrm.Aop.CurdBeforeHandler?.Invoke(_pgsqlInsert, before); + long ret = 0; + Exception exception = null; + try + { + ret = await _pgsqlInsert.InternalOrm.Ado.ExecuteNonQueryAsync(_pgsqlInsert.InternalConnection, _pgsqlInsert.InternalTransaction, CommandType.Text, sql, _pgsqlInsert._commandTimeout, _pgsqlInsert.InternalParams, cancellationToken); + } + catch (Exception ex) + { + exception = ex; + throw ex; + } + finally + { + var after = new CurdAfterEventArgs(before, exception, ret); + _pgsqlInsert.InternalOrm.Aop.CurdAfterHandler?.Invoke(_pgsqlInsert, after); + ClearData(); + } + return ret; + } +#endif + } +} diff --git a/Providers/FreeSql.Provider.QuestDb/Curd/QuestDbDelete.cs b/Providers/FreeSql.Provider.QuestDb/Curd/QuestDbDelete.cs new file mode 100644 index 00000000..85ff3692 --- /dev/null +++ b/Providers/FreeSql.Provider.QuestDb/Curd/QuestDbDelete.cs @@ -0,0 +1,32 @@ +using FreeSql.Internal; +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace FreeSql.PostgreSQL.Curd +{ + + class QuestDbDelete : Internal.CommonProvider.DeleteProvider + { + public QuestDbDelete(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) + : base(orm, commonUtils, commonExpression, dywhere) + { + + } + + public override List ExecuteDeleted() => throw new NotImplementedException("QuestDb 不支持删除数据."); + + public override int ExecuteAffrows() => throw new NotImplementedException("QuestDb 不支持删除数据."); + +#if net40 +#else + public override Task ExecuteAffrowsAsync(CancellationToken cancellationToken = default) => throw new NotImplementedException("QuestDb 不支持删除数据."); + + public override Task> ExecuteDeletedAsync(CancellationToken cancellationToken = default) => throw new NotImplementedException("QuestDb 不支持删除数据."); +#endif + } +} diff --git a/Providers/FreeSql.Provider.QuestDb/Curd/QuestDbInsert.cs b/Providers/FreeSql.Provider.QuestDb/Curd/QuestDbInsert.cs new file mode 100644 index 00000000..e95508c7 --- /dev/null +++ b/Providers/FreeSql.Provider.QuestDb/Curd/QuestDbInsert.cs @@ -0,0 +1,217 @@ +using FreeSql.Internal; +using FreeSql.Internal.Model; +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace FreeSql.QuestDb.Curd +{ + + class QuestDbInsert : Internal.CommonProvider.InsertProvider where T1 : class + { + public QuestDbInsert(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression) + : base(orm, commonUtils, commonExpression) + { + } + + internal IFreeSql InternalOrm => _orm; + internal TableInfo InternalTable => _table; + internal DbParameter[] InternalParams => _params; + internal DbConnection InternalConnection => _connection; + internal DbTransaction InternalTransaction => _transaction; + internal CommonUtils InternalCommonUtils => _commonUtils; + internal CommonExpression InternalCommonExpression => _commonExpression; + internal List InternalSource => _source; + internal Dictionary InternalIgnore => _ignore; + internal void InternalClearData() => ClearData(); + + public override int ExecuteAffrows() => base.SplitExecuteAffrows(_batchValuesLimit > 0 ? _batchValuesLimit : 5000, _batchParameterLimit > 0 ? _batchParameterLimit : 3000); + public override long ExecuteIdentity() => base.SplitExecuteIdentity(_batchValuesLimit > 0 ? _batchValuesLimit : 5000, _batchParameterLimit > 0 ? _batchParameterLimit : 3000); + public override List ExecuteInserted() => base.SplitExecuteInserted(_batchValuesLimit > 0 ? _batchValuesLimit : 5000, _batchParameterLimit > 0 ? _batchParameterLimit : 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.CurdBeforeHandler?.Invoke(this, before); + try + { + ret = _orm.Ado.ExecuteNonQuery(_connection, _transaction, CommandType.Text, sql, _commandTimeout, _params); + } + catch (Exception ex) + { + exception = ex; + throw ex; + } + finally + { + var after = new Aop.CurdAfterEventArgs(before, exception, ret); + _orm.Aop.CurdAfterHandler?.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.CurdBeforeHandler?.Invoke(this, before); + try + { + long.TryParse(string.Concat(_orm.Ado.ExecuteScalar(_connection, _transaction, CommandType.Text, sql, _commandTimeout, _params)), out ret); + } + catch (Exception ex) + { + exception = ex; + throw ex; + } + finally + { + var after = new Aop.CurdAfterEventArgs(before, exception, ret); + _orm.Aop.CurdAfterHandler?.Invoke(this, after); + } + return ret; + } + + protected override List RawExecuteInserted() + { + var sql = this.ToSql(); + if (string.IsNullOrEmpty(sql)) return new List(); + + var sb = new StringBuilder(); + sb.Append(sql).Append(" RETURNING "); + + var colidx = 0; + foreach (var col in _table.Columns.Values) + { + if (colidx > 0) sb.Append(", "); + sb.Append(_commonUtils.RereadColumn(col, _commonUtils.QuoteSqlName(col.Attribute.Name))).Append(" as ").Append(_commonUtils.QuoteSqlName(col.CsName)); + ++colidx; + } + sql = sb.ToString(); + var before = new Aop.CurdBeforeEventArgs(_table.Type, _table, Aop.CurdType.Insert, sql, _params); + _orm.Aop.CurdBeforeHandler?.Invoke(this, before); + var ret = new List(); + Exception exception = null; + try + { + ret = _orm.Ado.Query(_table.TypeLazy ?? _table.Type, _connection, _transaction, CommandType.Text, sql, _commandTimeout, _params); + } + catch (Exception ex) + { + exception = ex; + throw ex; + } + finally + { + var after = new Aop.CurdAfterEventArgs(before, exception, ret); + _orm.Aop.CurdAfterHandler?.Invoke(this, after); + } + return ret; + } + +#if net40 +#else + public override Task ExecuteAffrowsAsync(CancellationToken cancellationToken = default) => base.SplitExecuteAffrowsAsync(_batchValuesLimit > 0 ? _batchValuesLimit : 5000, _batchParameterLimit > 0 ? _batchParameterLimit : 3000, cancellationToken); + public override Task ExecuteIdentityAsync(CancellationToken cancellationToken = default) => base.SplitExecuteIdentityAsync(_batchValuesLimit > 0 ? _batchValuesLimit : 5000, _batchParameterLimit > 0 ? _batchParameterLimit : 3000, cancellationToken); + public override Task> ExecuteInsertedAsync(CancellationToken cancellationToken = default) => base.SplitExecuteInsertedAsync(_batchValuesLimit > 0 ? _batchValuesLimit : 5000, _batchParameterLimit > 0 ? _batchParameterLimit : 3000, cancellationToken); + + async protected override Task RawExecuteIdentityAsync(CancellationToken cancellationToken = default) + { + 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.CurdBeforeHandler?.Invoke(this, before); + try + { + ret = await _orm.Ado.ExecuteNonQueryAsync(_connection, _transaction, CommandType.Text, sql, _commandTimeout, _params, cancellationToken); + } + catch (Exception ex) + { + exception = ex; + throw ex; + } + finally + { + var after = new Aop.CurdAfterEventArgs(before, exception, ret); + _orm.Aop.CurdAfterHandler?.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.CurdBeforeHandler?.Invoke(this, before); + try + { + long.TryParse(string.Concat(await _orm.Ado.ExecuteScalarAsync(_connection, _transaction, CommandType.Text, sql, _commandTimeout, _params, cancellationToken)), out ret); + } + catch (Exception ex) + { + exception = ex; + throw ex; + } + finally + { + var after = new Aop.CurdAfterEventArgs(before, exception, ret); + _orm.Aop.CurdAfterHandler?.Invoke(this, after); + } + return ret; + } + async protected override Task> RawExecuteInsertedAsync(CancellationToken cancellationToken = default) + { + var sql = this.ToSql(); + if (string.IsNullOrEmpty(sql)) return new List(); + + var sb = new StringBuilder(); + sb.Append(sql).Append(" RETURNING "); + + var colidx = 0; + foreach (var col in _table.Columns.Values) + { + if (colidx > 0) sb.Append(", "); + sb.Append(_commonUtils.RereadColumn(col, _commonUtils.QuoteSqlName(col.Attribute.Name))).Append(" as ").Append(_commonUtils.QuoteSqlName(col.CsName)); + ++colidx; + } + sql = sb.ToString(); + var before = new Aop.CurdBeforeEventArgs(_table.Type, _table, Aop.CurdType.Insert, sql, _params); + _orm.Aop.CurdBeforeHandler?.Invoke(this, before); + var ret = new List(); + Exception exception = null; + try + { + ret = await _orm.Ado.QueryAsync(_table.TypeLazy ?? _table.Type, _connection, _transaction, CommandType.Text, sql, _commandTimeout, _params, cancellationToken); + } + catch (Exception ex) + { + exception = ex; + throw ex; + } + finally + { + var after = new Aop.CurdAfterEventArgs(before, exception, ret); + _orm.Aop.CurdAfterHandler?.Invoke(this, after); + } + return ret; + } +#endif + } +} diff --git a/Providers/FreeSql.Provider.QuestDb/Curd/QuestDbInsertOrUpdate.cs b/Providers/FreeSql.Provider.QuestDb/Curd/QuestDbInsertOrUpdate.cs new file mode 100644 index 00000000..a22d40ad --- /dev/null +++ b/Providers/FreeSql.Provider.QuestDb/Curd/QuestDbInsertOrUpdate.cs @@ -0,0 +1,72 @@ +using FreeSql.Internal; +using System.Collections.Generic; +using System.Data.Common; +using System.Linq; +using System.Text; + +namespace FreeSql.QuestDb.Curd +{ + + class QuestDbInsertOrUpdate : Internal.CommonProvider.InsertOrUpdateProvider where T1 : class + { + public QuestDbInsertOrUpdate(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression) + : base(orm, commonUtils, commonExpression) + { + } + + public override string ToSql() + { + var dbParams = new List(); + if (_sourceSql != null) + { + var data = new List(); + data.Add((T1)_table.Type.CreateInstanceGetDefaultValue()); + var sql = getInsertSql(data, false, false); + var sb = new StringBuilder(); + sb.Append(sql.Substring(0, sql.IndexOf(") VALUES"))); + sb.Append(") \r\n"); + WriteSourceSelectUnionAll(null, sb, null); + sb.Append(sql.Substring(sql.IndexOf("\r\nON CONFLICT(") + 2)); + return sb.ToString(); + } + if (_source?.Any() != true) return null; + + var sqls = new string[2]; + var ds = SplitSourceByIdentityValueIsNull(_source); + if (ds.Item1.Any()) sqls[0] = string.Join("\r\n\r\n;\r\n\r\n", ds.Item1.Select(a => getInsertSql(a, false, true))); + if (ds.Item2.Any()) sqls[1] = string.Join("\r\n\r\n;\r\n\r\n", ds.Item2.Select(a => getInsertSql(a, true, true))); + _params = dbParams.ToArray(); + if (ds.Item2.Any() == false) return sqls[0]; + if (ds.Item1.Any() == false) return sqls[1]; + return string.Join("\r\n\r\n;\r\n\r\n", sqls); + + string getInsertSql(List data, bool flagInsert, bool noneParameter) + { + var insert = _orm.Insert() + .AsTable(_tableRule).AsType(_table.Type) + .WithConnection(_connection) + .WithTransaction(_transaction) + .NoneParameter(noneParameter) as Internal.CommonProvider.InsertProvider; + insert._source = data; + insert._table = _table; + insert._noneParameterFlag = flagInsert ? "cuc" : "cu"; + + string sql = ""; + if (IdentityColumn != null && flagInsert) sql = insert.ToSql(); + else + { + var ocdu = new OnConflictDoUpdate(insert.InsertIdentity()); + ocdu._tempPrimarys = _tempPrimarys; + var cols = _table.Columns.Values.Where(a => _tempPrimarys.Contains(a) == false && a.Attribute.CanUpdate == true && _updateIgnore.ContainsKey(a.Attribute.Name) == false); + ocdu.UpdateColumns(cols.Select(a => a.Attribute.Name).ToArray()); + if (_doNothing == true || cols.Any() == false) + ocdu.DoNothing(); + sql = ocdu.ToSql(); + } + if (string.IsNullOrEmpty(sql)) return null; + if (insert._params?.Any() == true) dbParams.AddRange(insert._params); + return sql; + } + } + } +} \ No newline at end of file diff --git a/Providers/FreeSql.Provider.QuestDb/Curd/QuestDbSelect.cs b/Providers/FreeSql.Provider.QuestDb/Curd/QuestDbSelect.cs new file mode 100644 index 00000000..0dd74268 --- /dev/null +++ b/Providers/FreeSql.Provider.QuestDb/Curd/QuestDbSelect.cs @@ -0,0 +1,645 @@ +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; +using System.Threading; + +namespace FreeSql.QuestDb.Curd +{ + class QuestDbSelect : FreeSql.Internal.CommonProvider.Select1Provider + { + 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, string _tosqlAppendContent, + List _whereGlobalFilter, IFreeSql _orm) + { + if (_orm.CodeFirst.IsAutoSyncStructure) + _orm.CodeFirst.SyncStructure(_tables.Select(a => a.Table.Type).ToArray()); + + if (_whereGlobalFilter.Any()) + foreach (var tb in _tables.Where(a => a.Type != SelectTableInfoType.Parent)) + tb.Cascade = _commonExpression.GetWhereCascadeSql(tb, _whereGlobalFilter, true); + + 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 (_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); + } + } + } + + 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); + } + + if (a < tbsfrom.Length - 1) sb.Append(", "); + } + + foreach (var tb in tbsjoin) + { + switch (tb.Type) + { + case SelectTableInfoType.Parent: + case SelectTableInfoType.RawJoin: + continue; + 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); + 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); + + 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) + { + var message = @"提示:由于QuedtDb语法不支持Having,请使用以下语法代替: +fsql.Select() + //.GroupBy(a => a.Id) + //.Having(a => a.Count() > 1) + .WithTempQuery(a => new + { + a.Id, + count1 = SqlExt.Count(a.Id).ToValue() + }) + .Where(a => a.count1 > 1) + .ToList();"; + throw new NotImplementedException(message); + } + } + + sb.Append(_orderby); + + //SamoleBy扩展 + if (SampleByExtension.IsExistence.Value) + { + sb.Append(SampleByExtension.SamoleByString.Value); + SampleByExtension.Initialize(); + } + + //LatestOn扩展 + if (LatestOnExtension.IsExistence.Value) + { + sb.Append(LatestOnExtension.LatestOnString.Value); + LatestOnExtension.Initialize(); + } + + if (_limit > 0) + sb.Append(" \r\nlimit ").Append(_skip == 0 ? _limit : _skip); + if (_skip > 0) + //sb.Append(" \r\noffset ").Append(_skip); + sb.Append(",").Append(_skip + _limit); + + sbnav.Clear(); + if (tbUnionsGt0) sb.Append(") ftb"); + } + + return sb.Append(_tosqlAppendContent).ToString(); + } + + public QuestDbSelect(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 QuestDbSelect(_orm, _commonUtils, _commonExpression, null); + QuestDbSelect.CopyData(this, ret, exp?.Parameters); + return ret; + } + + public override ISelect From( + Expression, T2, T3, ISelectFromExpression>> exp) + { + this.InternalFrom(exp); + var ret = new QuestDbSelect(_orm, _commonUtils, _commonExpression, null); + QuestDbSelect.CopyData(this, ret, exp?.Parameters); + return ret; + } + + public override ISelect From( + Expression, T2, T3, T4, ISelectFromExpression>> exp) + { + this.InternalFrom(exp); + var ret = new QuestDbSelect(_orm, _commonUtils, _commonExpression, null); + QuestDbSelect.CopyData(this, ret, exp?.Parameters); + return ret; + } + + public override ISelect From( + Expression, T2, T3, T4, T5, ISelectFromExpression>> exp) + { + this.InternalFrom(exp); + var ret = new QuestDbSelect(_orm, _commonUtils, _commonExpression, null); + QuestDbSelect.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 QuestDbSelect(_orm, _commonUtils, _commonExpression, null); + QuestDbSelect.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 QuestDbSelect(_orm, _commonUtils, _commonExpression, null); + QuestDbSelect.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 QuestDbSelect(_orm, _commonUtils, _commonExpression, null); + QuestDbSelect.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 QuestDbSelect(_orm, _commonUtils, _commonExpression, + null); + QuestDbSelect.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 QuestDbSelect(_orm, _commonUtils, + _commonExpression, null); + QuestDbSelect.CopyData(this, ret, exp?.Parameters); + return ret; + } + + public override ISelect + From( + Expression, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, + ISelectFromExpression>> exp) + { + this.InternalFrom(exp); + var ret = new QuestDbSelect(_orm, _commonUtils, + _commonExpression, null); + QuestDbSelect.CopyData(this, ret, exp?.Parameters); + return ret; + } + + public override ISelect + From( + Expression, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, + ISelectFromExpression>> exp) + { + this.InternalFrom(exp); + var ret = new QuestDbSelect(_orm, _commonUtils, + _commonExpression, null); + QuestDbSelect.CopyData(this, ret, exp?.Parameters); + return ret; + } + + public override ISelect + From( + Expression, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, + ISelectFromExpression>> exp) + { + this.InternalFrom(exp); + var ret = new QuestDbSelect(_orm, _commonUtils, + _commonExpression, null); + QuestDbSelect.CopyData(this, ret, exp?.Parameters); + return ret; + } + + public override ISelect + From( + Expression, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, + ISelectFromExpression>> exp) + { + this.InternalFrom(exp); + var ret = new QuestDbSelect(_orm, + _commonUtils, _commonExpression, null); + QuestDbSelect.CopyData(this, ret, exp?.Parameters); + return ret; + } + + public override ISelect From( + Expression, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, + ISelectFromExpression>> exp) + { + this.InternalFrom(exp); + var ret = new QuestDbSelect(_orm, + _commonUtils, _commonExpression, null); + QuestDbSelect.CopyData(this, ret, exp?.Parameters); + return ret; + } + + public override ISelect From( + Expression, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16 + , ISelectFromExpression>> exp) + { + this.InternalFrom(exp); + var ret = new QuestDbSelect(_orm, + _commonUtils, _commonExpression, null); + QuestDbSelect.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, _tosqlAppendContent, + _whereGlobalFilter, _orm); + } + + class QuestDbSelect : FreeSql.Internal.CommonProvider.Select2Provider where T2 : class + { + public QuestDbSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, + object dywhere) : base(orm, commonUtils, commonExpression, dywhere) + { + } + + public override string ToSql(string field = null) => QuestDbSelect.ToSqlStatic(_commonUtils, + _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, + _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, + _tosqlAppendContent, _whereGlobalFilter, _orm); + } + + class QuestDbSelect : FreeSql.Internal.CommonProvider.Select3Provider + where T2 : class where T3 : class + { + public QuestDbSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, + object dywhere) : base(orm, commonUtils, commonExpression, dywhere) + { + } + + public override string ToSql(string field = null) => QuestDbSelect.ToSqlStatic(_commonUtils, + _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, + _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, + _tosqlAppendContent, _whereGlobalFilter, _orm); + } + + class QuestDbSelect : FreeSql.Internal.CommonProvider.Select4Provider + where T2 : class where T3 : class where T4 : class + { + public QuestDbSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, + object dywhere) : base(orm, commonUtils, commonExpression, dywhere) + { + } + + public override string ToSql(string field = null) => QuestDbSelect.ToSqlStatic(_commonUtils, + _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, + _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, + _tosqlAppendContent, _whereGlobalFilter, _orm); + } + + class QuestDbSelect : FreeSql.Internal.CommonProvider.Select5Provider + where T2 : class where T3 : class where T4 : class where T5 : class + { + public QuestDbSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, + object dywhere) : base(orm, commonUtils, commonExpression, dywhere) + { + } + + public override string ToSql(string field = null) => QuestDbSelect.ToSqlStatic(_commonUtils, + _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, + _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, + _tosqlAppendContent, _whereGlobalFilter, _orm); + } + + class QuestDbSelect : FreeSql.Internal.CommonProvider.Select6Provider where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class + { + public QuestDbSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, + object dywhere) : base(orm, commonUtils, commonExpression, dywhere) + { + } + + public override string ToSql(string field = null) => QuestDbSelect.ToSqlStatic(_commonUtils, + _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, + _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, + _tosqlAppendContent, _whereGlobalFilter, _orm); + } + + class QuestDbSelect : FreeSql.Internal.CommonProvider.Select7Provider where T2 : class + where T3 : class + where T4 : class + where T5 : class + where T6 : class + where T7 : class + { + public QuestDbSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, + object dywhere) : base(orm, commonUtils, commonExpression, dywhere) + { + } + + public override string ToSql(string field = null) => QuestDbSelect.ToSqlStatic(_commonUtils, + _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, + _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, + _tosqlAppendContent, _whereGlobalFilter, _orm); + } + + class QuestDbSelect : FreeSql.Internal.CommonProvider.Select8Provider where T2 : class + where T3 : class + where T4 : class + where T5 : class + where T6 : class + where T7 : class + where T8 : class + { + public QuestDbSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, + object dywhere) : base(orm, commonUtils, commonExpression, dywhere) + { + } + + public override string ToSql(string field = null) => QuestDbSelect.ToSqlStatic(_commonUtils, + _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, + _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, + _tosqlAppendContent, _whereGlobalFilter, _orm); + } + + class QuestDbSelect : FreeSql.Internal.CommonProvider.Select9Provider 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 QuestDbSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, + object dywhere) : base(orm, commonUtils, commonExpression, dywhere) + { + } + + public override string ToSql(string field = null) => QuestDbSelect.ToSqlStatic(_commonUtils, + _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, + _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, + _tosqlAppendContent, _whereGlobalFilter, _orm); + } + + class QuestDbSelect : FreeSql.Internal.CommonProvider.Select10Provider< + T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> 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 QuestDbSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, + object dywhere) : base(orm, commonUtils, commonExpression, dywhere) + { + } + + public override string ToSql(string field = null) => QuestDbSelect.ToSqlStatic(_commonUtils, + _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, + _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, + _tosqlAppendContent, _whereGlobalFilter, _orm); + } + + class QuestDbSelect : FreeSql.Internal.CommonProvider. + Select11Provider 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 + where T11 : class + { + public QuestDbSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, + object dywhere) : base(orm, commonUtils, commonExpression, dywhere) + { + } + + public override string ToSql(string field = null) => QuestDbSelect.ToSqlStatic(_commonUtils, + _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, + _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, + _tosqlAppendContent, _whereGlobalFilter, _orm); + } + + class QuestDbSelect : FreeSql.Internal.CommonProvider. + Select12Provider 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 + where T11 : class + where T12 : class + { + public QuestDbSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, + object dywhere) : base(orm, commonUtils, commonExpression, dywhere) + { + } + + public override string ToSql(string field = null) => QuestDbSelect.ToSqlStatic(_commonUtils, + _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, + _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, + _tosqlAppendContent, _whereGlobalFilter, _orm); + } + + class QuestDbSelect : FreeSql.Internal.CommonProvider. + Select13Provider 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 + where T11 : class + where T12 : class + where T13 : class + { + public QuestDbSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, + object dywhere) : base(orm, commonUtils, commonExpression, dywhere) + { + } + + public override string ToSql(string field = null) => QuestDbSelect.ToSqlStatic(_commonUtils, + _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, + _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, + _tosqlAppendContent, _whereGlobalFilter, _orm); + } + + class QuestDbSelect : FreeSql.Internal. + CommonProvider.Select14Provider 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 + where T11 : class + where T12 : class + where T13 : class + where T14 : class + { + public QuestDbSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, + object dywhere) : base(orm, commonUtils, commonExpression, dywhere) + { + } + + public override string ToSql(string field = null) => QuestDbSelect.ToSqlStatic(_commonUtils, + _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, + _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, + _tosqlAppendContent, _whereGlobalFilter, _orm); + } + + class QuestDbSelect : FreeSql.Internal. + CommonProvider.Select15Provider + 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 + where T11 : class + where T12 : class + where T13 : class + where T14 : class + where T15 : class + { + public QuestDbSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, + object dywhere) : base(orm, commonUtils, commonExpression, dywhere) + { + } + + public override string ToSql(string field = null) => QuestDbSelect.ToSqlStatic(_commonUtils, + _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, + _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, + _tosqlAppendContent, _whereGlobalFilter, _orm); + } + + class QuestDbSelect : FreeSql.Internal. + CommonProvider.Select16Provider + 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 + where T11 : class + where T12 : class + where T13 : class + where T14 : class + where T15 : class + where T16 : class + { + public QuestDbSelect(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, + object dywhere) : base(orm, commonUtils, commonExpression, dywhere) + { + } + + public override string ToSql(string field = null) => QuestDbSelect.ToSqlStatic(_commonUtils, + _commonExpression, _select, _distinct, field ?? this.GetAllFieldExpressionTreeLevel2().Field, _join, _where, + _groupby, _having, _orderby, _skip, _limit, _tables, this.GetTableRuleUnions(), _aliasRule, + _tosqlAppendContent, _whereGlobalFilter, _orm); + } +} \ No newline at end of file diff --git a/Providers/FreeSql.Provider.QuestDb/Curd/QuestDbUpdate.cs b/Providers/FreeSql.Provider.QuestDb/Curd/QuestDbUpdate.cs new file mode 100644 index 00000000..262ee7a5 --- /dev/null +++ b/Providers/FreeSql.Provider.QuestDb/Curd/QuestDbUpdate.cs @@ -0,0 +1,187 @@ +using FreeSql.Internal; +using FreeSql.Internal.Model; +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace FreeSql.QuestDb.Curd +{ + + class QuestDbUpdate : Internal.CommonProvider.UpdateProvider + { + + public QuestDbUpdate(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) + : base(orm, commonUtils, commonExpression, dywhere) + { + } + + internal string InternalTableAlias { get; set; } + internal StringBuilder InternalSbSet => _set; + internal StringBuilder InternalSbSetIncr => _setIncr; + internal Dictionary InternalIgnore => _ignore; + internal void InternalResetSource(List source) => _source = source; + internal string InternalWhereCaseSource(string CsName, Func thenValue) => WhereCaseSource(CsName, thenValue); + internal void InternalToSqlCaseWhenEnd(StringBuilder sb, ColumnInfo col) => ToSqlCaseWhenEnd(sb, col); + + public override int ExecuteAffrows() => base.SplitExecuteAffrows(_batchRowsLimit > 0 ? _batchRowsLimit : 500, _batchParameterLimit > 0 ? _batchParameterLimit : 3000); + public override List ExecuteUpdated() => base.SplitExecuteUpdated(_batchRowsLimit > 0 ? _batchRowsLimit : 500, _batchParameterLimit > 0 ? _batchParameterLimit : 3000); + + protected override List RawExecuteUpdated() + { + var ret = new List(); + DbParameter[] dbParms = null; + StringBuilder sbret = null; + ToSqlFetch(sb => + { + if (dbParms == null) + { + dbParms = _params.Concat(_paramsSource).ToArray(); + sbret = new StringBuilder(); + sbret.Append(" RETURNING "); + + var colidx = 0; + foreach (var col in _table.Columns.Values) + { + if (colidx > 0) sbret.Append(", "); + sbret.Append(_commonUtils.RereadColumn(col, _commonUtils.QuoteSqlName(col.Attribute.Name))).Append(" as ").Append(_commonUtils.QuoteSqlName(col.CsName)); + ++colidx; + } + } + var sql = sb.Append(sbret).ToString(); + var before = new Aop.CurdBeforeEventArgs(_table.Type, _table, Aop.CurdType.Update, sql, dbParms); + _orm.Aop.CurdBeforeHandler?.Invoke(this, before); + + Exception exception = null; + try + { + var rettmp = _orm.Ado.Query(_table.TypeLazy ?? _table.Type, _connection, _transaction, CommandType.Text, sql, _commandTimeout, dbParms); + ValidateVersionAndThrow(rettmp.Count, sql, dbParms); + ret.AddRange(rettmp); + } + catch (Exception ex) + { + exception = ex; + throw; + } + finally + { + var after = new Aop.CurdAfterEventArgs(before, exception, ret); + _orm.Aop.CurdAfterHandler?.Invoke(this, after); + } + }); + sbret?.Clear(); + return ret; + } + + protected override void ToSqlCase(StringBuilder caseWhen, ColumnInfo[] primarys) + { + if (primarys.Length == 1) + { + var pk = primarys.First(); + if (string.IsNullOrEmpty(InternalTableAlias) == false) caseWhen.Append(InternalTableAlias).Append("."); + caseWhen.Append(_commonUtils.RereadColumn(pk, _commonUtils.QuoteSqlName(pk.Attribute.Name))); + return; + } + caseWhen.Append("("); + var pkidx = 0; + foreach (var pk in primarys) + { + if (pkidx > 0) caseWhen.Append(" || '+' || "); + if (string.IsNullOrEmpty(InternalTableAlias) == false) caseWhen.Append(InternalTableAlias).Append("."); + caseWhen.Append(_commonUtils.RereadColumn(pk, _commonUtils.QuoteSqlName(pk.Attribute.Name))).Append("::text"); + ++pkidx; + } + caseWhen.Append(")"); + } + + protected override void ToSqlWhen(StringBuilder sb, ColumnInfo[] primarys, object d) + { + if (primarys.Length == 1) + { + sb.Append(_commonUtils.FormatSql("{0}", primarys[0].GetDbValue(d))); + return; + } + sb.Append("("); + var pkidx = 0; + foreach (var pk in primarys) + { + if (pkidx > 0) sb.Append(" || '+' || "); + sb.Append(_commonUtils.FormatSql("{0}", pk.GetDbValue(d))).Append("::text"); + ++pkidx; + } + sb.Append(")"); + } + + protected override void ToSqlCaseWhenEnd(StringBuilder sb, ColumnInfo col) + { + if (_noneParameter == false) return; + if (col.Attribute.MapType == typeof(string)) + { + sb.Append("::text"); + 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(CancellationToken cancellationToken = default) => base.SplitExecuteAffrowsAsync(_batchRowsLimit > 0 ? _batchRowsLimit : 500, _batchParameterLimit > 0 ? _batchParameterLimit : 3000, cancellationToken); + public override Task> ExecuteUpdatedAsync(CancellationToken cancellationToken = default) => base.SplitExecuteUpdatedAsync(_batchRowsLimit > 0 ? _batchRowsLimit : 500, _batchParameterLimit > 0 ? _batchParameterLimit : 3000, cancellationToken); + + async protected override Task> RawExecuteUpdatedAsync(CancellationToken cancellationToken = default) + { + var ret = new List(); + DbParameter[] dbParms = null; + StringBuilder sbret = null; + await ToSqlFetchAsync(async sb => + { + if (dbParms == null) + { + dbParms = _params.Concat(_paramsSource).ToArray(); + sbret = new StringBuilder(); + sbret.Append(" RETURNING "); + + var colidx = 0; + foreach (var col in _table.Columns.Values) + { + if (colidx > 0) sbret.Append(", "); + sbret.Append(_commonUtils.RereadColumn(col, _commonUtils.QuoteSqlName(col.Attribute.Name))).Append(" as ").Append(_commonUtils.QuoteSqlName(col.CsName)); + ++colidx; + } + } + var sql = sb.Append(sbret).ToString(); + var before = new Aop.CurdBeforeEventArgs(_table.Type, _table, Aop.CurdType.Update, sql, dbParms); + _orm.Aop.CurdBeforeHandler?.Invoke(this, before); + + Exception exception = null; + try + { + var rettmp = await _orm.Ado.QueryAsync(_table.TypeLazy ?? _table.Type, _connection, _transaction, CommandType.Text, sql, _commandTimeout, dbParms, cancellationToken); + ValidateVersionAndThrow(rettmp.Count, sql, dbParms); + ret.AddRange(rettmp); + } + catch (Exception ex) + { + exception = ex; + throw; + } + finally + { + var after = new Aop.CurdAfterEventArgs(before, exception, ret); + _orm.Aop.CurdAfterHandler?.Invoke(this, after); + } + }); + sbret?.Clear(); + return ret; + } +#endif + } +} diff --git a/Providers/FreeSql.Provider.QuestDb/FreeSql.Provider.QuestDb.csproj b/Providers/FreeSql.Provider.QuestDb/FreeSql.Provider.QuestDb.csproj new file mode 100644 index 00000000..bdad827e --- /dev/null +++ b/Providers/FreeSql.Provider.QuestDb/FreeSql.Provider.QuestDb.csproj @@ -0,0 +1,54 @@ + + + + netstandard2.0; + true + YeXiangQin;Daily + FreeSql适配QuestDb时序数据库访问 + https://github.com/2881099/FreeSql + https://github.com/2881099/FreeSql + git + MIT + FreeSql;ORM;QuestDb + $(AssemblyName) + logo.png + $(AssemblyName) + true + true + true + key.snk + false + 3.2.688-preview20230216 + + + + + + + + + + + + + + + + + + + + + + + + + + + net45 + + + nts + + + diff --git a/Providers/FreeSql.Provider.QuestDb/QuestDbAdo/QuestDbAdo.cs b/Providers/FreeSql.Provider.QuestDb/QuestDbAdo/QuestDbAdo.cs new file mode 100644 index 00000000..aa657b6d --- /dev/null +++ b/Providers/FreeSql.Provider.QuestDb/QuestDbAdo/QuestDbAdo.cs @@ -0,0 +1,109 @@ +using FreeSql.Internal; +using FreeSql.Internal.Model; +using Newtonsoft.Json.Linq; +using Npgsql; +using FreeSql.Internal.ObjectPool; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data.Common; +using System.Linq; +using System.Text; +using System.Threading; + +namespace FreeSql.QuestDb +{ + class QuestDbAdo : FreeSql.Internal.CommonProvider.AdoProvider + { + public QuestDbAdo() : base(DataType.PostgreSQL, null, null) { } + public QuestDbAdo(CommonUtils util, string masterConnectionString, string[] slaveConnectionStrings, Func connectionFactory) : base(DataType.PostgreSQL, masterConnectionString, slaveConnectionStrings) + { + base._util = util; + if (connectionFactory != null) + { + var pool = new FreeSql.Internal.CommonProvider.DbConnectionPool(DataType.PostgreSQL, connectionFactory); + ConnectionString = pool.TestConnection?.ConnectionString; + MasterPool = pool; + return; + } + if (!string.IsNullOrEmpty(masterConnectionString)) + MasterPool = new QuestDbConnectionPool(CoreStrings.S_MasterDatabase, masterConnectionString, null, null); + if (slaveConnectionStrings != null) + { + foreach (var slaveConnectionString in slaveConnectionStrings) + { + var slavePool = new QuestDbConnectionPool($"{CoreStrings.S_SlaveDatabase}{SlavePools.Count + 1}", slaveConnectionString, () => Interlocked.Decrement(ref slaveUnavailables), () => Interlocked.Increment(ref slaveUnavailables)); + SlavePools.Add(slavePool); + } + } + } + + 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 || param is JToken || param is JObject || param is JArray)) + param = Utils.GetDataReaderValue(mapType, param); + + bool isdic; + if (param is bool || param is bool?) + return (bool)param; + else if (param is string) + return string.Concat("'", param.ToString().Replace("'", "''"), "'"); + else if (param is char) + return string.Concat("'", param.ToString().Replace("'", "''").Replace('\0', ' '), "'"); + 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 byte[]) + return $"'\\x{CommonUtils.BytesSqlRaw(param as byte[])}'"; + else if (param is JToken || param is JObject || param is JArray) + return string.Concat("'", param.ToString().Replace("'", "''"), "'::jsonb"); + else if ((isdic = param is Dictionary) || + param is IEnumerable>) + { + var pgdics = isdic ? param as Dictionary : + param as IEnumerable>; + + var pghstore = new StringBuilder("'"); + var pairs = pgdics.ToArray(); + + for (var i = 0; i < pairs.Length; i++) + { + if (i != 0) pghstore.Append(","); + + pghstore.AppendFormat("\"{0}\"=>", pairs[i].Key.Replace("'", "''")); + + if (pairs[i].Value == null) + pghstore.Append("NULL"); + else + pghstore.AppendFormat("\"{0}\"", pairs[i].Value.Replace("'", "''")); + } + + return pghstore.Append("'::hstore"); + } + else if (param is IEnumerable) + return AddslashesIEnumerable(param, mapType, mapColumn); + + return string.Concat("'", param.ToString().Replace("'", "''"), "'"); + } + + public override DbCommand CreateCommand() + { + return new NpgsqlCommand(); + } + + public override void ReturnConnection(IObjectPool pool, Object conn, Exception ex) + { + var rawPool = pool as QuestDbConnectionPool; + if (rawPool != null) rawPool.Return(conn, ex); + else pool.Return(conn); + } + + public override DbParameter[] GetDbParamtersByObject(string sql, object obj) => _util.GetDbParamtersByObject(sql, obj); + } +} \ No newline at end of file diff --git a/Providers/FreeSql.Provider.QuestDb/QuestDbAdo/QuestDbConnectionPool.cs b/Providers/FreeSql.Provider.QuestDb/QuestDbAdo/QuestDbConnectionPool.cs new file mode 100644 index 00000000..3f777e74 --- /dev/null +++ b/Providers/FreeSql.Provider.QuestDb/QuestDbAdo/QuestDbConnectionPool.cs @@ -0,0 +1,241 @@ +using Npgsql; +using FreeSql.Internal.ObjectPool; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace FreeSql.QuestDb +{ + + class QuestDbConnectionPool : ObjectPool + { + + internal Action availableHandler; + internal Action unavailableHandler; + + public QuestDbConnectionPool(string name, string connectionString, Action availableHandler, Action unavailableHandler) : base(null) + { + this.availableHandler = availableHandler; + this.unavailableHandler = unavailableHandler; + var policy = new PostgreSQLConnectionPoolPolicy + { + _pool = this, + Name = name + }; + this.Policy = policy; + policy.ConnectionString = connectionString; + } + + public void Return(Object obj, Exception exception, bool isRecreate = false) + { + if (exception != null && exception is NpgsqlException) + { + if (obj.Value.Ping() == false) + base.SetUnavailable(exception, obj.LastGetTimeCopy); + } + base.Return(obj, isRecreate); + } + } + + class PostgreSQLConnectionPoolPolicy : IPolicy + { + + internal QuestDbConnectionPool _pool; + public string Name { get; set; } = $"PostgreSQL NpgsqlConnection {CoreStrings.S_ObjectPool}"; + public int PoolSize { get; set; } = 50; + public TimeSpan SyncGetTimeout { get; set; } = TimeSpan.FromSeconds(10); + public TimeSpan IdleTimeout { get; set; } = TimeSpan.FromSeconds(20); + public int AsyncGetCapacity { get; set; } = 10000; + public bool IsThrowGetTimeoutException { get; set; } = true; + public bool IsAutoDisposeWithSystem { get; set; } = true; + public int CheckAvailableInterval { get; set; } = 2; + public int Weight { get; set; } = 1; + + static ConcurrentDictionary dicConnStrIncr = new ConcurrentDictionary(StringComparer.CurrentCultureIgnoreCase); + private string _connectionString; + public string ConnectionString + { + get => _connectionString; + set + { + _connectionString = value ?? ""; + + var minPoolSize = 0; + var pattern = @"Min(imum)?\s*pool\s*size\s*=\s*(\d+)"; + var m = Regex.Match(_connectionString, pattern, RegexOptions.IgnoreCase); + if (m.Success) + { + minPoolSize = int.Parse(m.Groups[2].Value); + _connectionString = Regex.Replace(_connectionString, pattern, "", RegexOptions.IgnoreCase); + } + + pattern = @"Max(imum)?\s*pool\s*size\s*=\s*(\d+)"; + m = Regex.Match(_connectionString, pattern, RegexOptions.IgnoreCase); + if (m.Success == false || int.TryParse(m.Groups[2].Value, out var poolsize) == false || poolsize <= 0) poolsize = Math.Max(50, minPoolSize); + var connStrIncr = dicConnStrIncr.AddOrUpdate(_connectionString, 1, (oldkey, oldval) => Math.Min(5, oldval + 1)); + PoolSize = poolsize + connStrIncr; + _connectionString = m.Success ? + Regex.Replace(_connectionString, pattern, $"Maximum pool size={PoolSize}", RegexOptions.IgnoreCase) : + $"{_connectionString};Maximum 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); + } + + FreeSql.Internal.CommonUtils.PrevReheatConnectionPool(_pool, minPoolSize); + } + } + + public bool OnCheckAvailable(Object obj) + { + if (obj.Value == null) return false; + if (obj.Value.State == ConnectionState.Closed) obj.Value.Open(); + return obj.Value.Ping(true); + } + + public DbConnection OnCreate() + { + var conn = new NpgsqlConnection(_connectionString); + return conn; + } + + public void OnDestroy(DbConnection obj) + { + try { if (obj.State != ConnectionState.Closed) obj.Close(); } catch { } + try { NpgsqlConnection.ClearPool(obj as NpgsqlConnection); } catch { } + obj.Dispose(); + } + + public void OnGet(Object obj) + { + + if (_pool.IsAvailable) + { + if (obj.Value == null) + { + _pool.SetUnavailable(new Exception(CoreStrings.S_ConnectionStringError), obj.LastGetTimeCopy); + throw new Exception(CoreStrings.S_ConnectionStringError_Check(this.Name)); + } + + 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, obj.LastGetTimeCopy) == true) + throw new Exception($"【{this.Name}】Block access and wait for recovery: {ex.Message}"); + throw ex; + } + } + } + } + +#if net40 +#else + async public Task OnGetAsync(Object obj) + { + + if (_pool.IsAvailable) + { + if (obj.Value == null) + { + _pool.SetUnavailable(new Exception(CoreStrings.S_ConnectionStringError), obj.LastGetTimeCopy); + throw new Exception(CoreStrings.S_ConnectionStringError_Check(this.Name)); + } + + 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, obj.LastGetTimeCopy) == true) + throw new Exception($"【{this.Name}】Block access and wait for recovery: {ex.Message}"); + throw ex; + } + } + } + } +#endif + + public void OnGetTimeout() + { + + } + + public void OnReturn(Object obj) + { + //if (obj?.Value != null && obj.Value.State != ConnectionState.Closed) try { obj.Value.Close(); } catch { } + } + + 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.QuestDb/QuestDbAdo/QuestDbTypesConverter.cs b/Providers/FreeSql.Provider.QuestDb/QuestDbAdo/QuestDbTypesConverter.cs new file mode 100644 index 00000000..75f11aa8 --- /dev/null +++ b/Providers/FreeSql.Provider.QuestDb/QuestDbAdo/QuestDbTypesConverter.cs @@ -0,0 +1,157 @@ +using Newtonsoft.Json.Linq; +using Npgsql; +using NpgsqlTypes; +using System; +using System.Collections; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; + +namespace Newtonsoft.Json +{ + public class QuestDbTypesConverter : JsonConverter + { + private static readonly Type typeof_BitArray = typeof(BitArray); + + private static readonly Type typeof_NpgsqlPoint = typeof(NpgsqlPoint); + private static readonly Type typeof_NpgsqlLine = typeof(NpgsqlLine); + private static readonly Type typeof_NpgsqlLSeg = typeof(NpgsqlLSeg); + private static readonly Type typeof_NpgsqlBox = typeof(NpgsqlBox); + private static readonly Type typeof_NpgsqlPath = typeof(NpgsqlPath); + private static readonly Type typeof_NpgsqlPolygon = typeof(NpgsqlPolygon); + private static readonly Type typeof_NpgsqlCircle = typeof(NpgsqlCircle); + + private static readonly Type typeof_Cidr = typeof((IPAddress, int)); + private static readonly Type typeof_IPAddress = typeof(IPAddress); + private static readonly Type typeof_PhysicalAddress = typeof(PhysicalAddress); + + private static readonly Type typeof_String = typeof(string); + + private static readonly Type typeof_NpgsqlRange_int = typeof(NpgsqlRange); + private static readonly Type typeof_NpgsqlRange_long = typeof(NpgsqlRange); + private static readonly Type typeof_NpgsqlRange_decimal = typeof(NpgsqlRange); + private static readonly Type typeof_NpgsqlRange_DateTime = typeof(NpgsqlRange); + public override bool CanConvert(Type objectType) + { + Type ctype = objectType.IsArray ? objectType.GetElementType() : objectType; + var ctypeGenericType1 = ctype.GenericTypeArguments.FirstOrDefault(); + + if (ctype == typeof_BitArray) return true; + + if (ctype == typeof_NpgsqlPoint || ctypeGenericType1 == typeof_NpgsqlPoint) return true; + if (ctype == typeof_NpgsqlLine || ctypeGenericType1 == typeof_NpgsqlLine) return true; + if (ctype == typeof_NpgsqlLSeg || ctypeGenericType1 == typeof_NpgsqlLSeg) return true; + if (ctype == typeof_NpgsqlBox || ctypeGenericType1 == typeof_NpgsqlBox) return true; + if (ctype == typeof_NpgsqlPath || ctypeGenericType1 == typeof_NpgsqlPath) return true; + if (ctype == typeof_NpgsqlPolygon || ctypeGenericType1 == typeof_NpgsqlPolygon) return true; + if (ctype == typeof_NpgsqlCircle || ctypeGenericType1 == typeof_NpgsqlCircle) return true; + + if (ctype == typeof_Cidr || ctypeGenericType1 == typeof_Cidr) return true; + if (ctype == typeof_IPAddress) return true; + if (ctype == typeof_PhysicalAddress) return true; + + if (ctype == typeof_NpgsqlRange_int || ctypeGenericType1 == typeof_NpgsqlRange_int) return true; + if (ctype == typeof_NpgsqlRange_long || ctypeGenericType1 == typeof_NpgsqlRange_long) return true; + if (ctype == typeof_NpgsqlRange_decimal || ctypeGenericType1 == typeof_NpgsqlRange_decimal) return true; + if (ctype == typeof_NpgsqlRange_DateTime || ctypeGenericType1 == typeof_NpgsqlRange_DateTime) return true; + + return false; + } + private object YieldJToken(Type ctype, JToken jt, int rank) + { + if (jt.Type == JTokenType.Null) return null; + if (rank == 0) + { + var ctypeGenericType1 = ctype.GenericTypeArguments.FirstOrDefault();//ctype.Namespace == "System" && ctype.Name.StartsWith("Nullable`") ? ctype.GenericTypeArguments.FirstOrDefault() : null; + if (ctype == typeof_BitArray) return jt.ToString().ToBitArray(); + + if (ctype == typeof_NpgsqlPoint || ctypeGenericType1 == typeof_NpgsqlPoint) return NpgsqlPoint.Parse(jt.ToString()); + if (ctype == typeof_NpgsqlLine || ctypeGenericType1 == typeof_NpgsqlLine) return NpgsqlLine.Parse(jt.ToString()); + if (ctype == typeof_NpgsqlLSeg || ctypeGenericType1 == typeof_NpgsqlLSeg) return NpgsqlLSeg.Parse(jt.ToString()); + if (ctype == typeof_NpgsqlBox || ctypeGenericType1 == typeof_NpgsqlBox) return NpgsqlBox.Parse(jt.ToString()); + if (ctype == typeof_NpgsqlPath || ctypeGenericType1 == typeof_NpgsqlPath) return NpgsqlPath.Parse(jt.ToString()); + if (ctype == typeof_NpgsqlPolygon || ctypeGenericType1 == typeof_NpgsqlPolygon) return NpgsqlPolygon.Parse(jt.ToString()); + if (ctype == typeof_NpgsqlCircle || ctypeGenericType1 == typeof_NpgsqlCircle) return NpgsqlCircle.Parse(jt.ToString()); + + if (ctype == typeof_Cidr || ctypeGenericType1 == typeof_Cidr) + { + var cidrArgs = jt.ToString().Split(new[] { '/' }, 2); + return (IPAddress.Parse(cidrArgs.First()), cidrArgs.Length >= 2 ? int.TryParse(cidrArgs[1], out var tryCdirSubnet) ? tryCdirSubnet : 0 : 0); + } + if (ctype == typeof_IPAddress) return IPAddress.Parse(jt.ToString()); + if (ctype == typeof_PhysicalAddress) return PhysicalAddress.Parse(jt.ToString()); + + if (ctype == typeof_NpgsqlRange_int || ctypeGenericType1 == typeof_NpgsqlRange_int) return jt.ToString().ToNpgsqlRange(); + if (ctype == typeof_NpgsqlRange_long || ctypeGenericType1 == typeof_NpgsqlRange_long) return jt.ToString().ToNpgsqlRange(); + if (ctype == typeof_NpgsqlRange_decimal || ctypeGenericType1 == typeof_NpgsqlRange_decimal) return jt.ToString().ToNpgsqlRange(); + if (ctype == typeof_NpgsqlRange_DateTime || ctypeGenericType1 == typeof_NpgsqlRange_DateTime) return jt.ToString().ToNpgsqlRange(); + + return null; + } + + var jtarr = jt.ToArray(); + var ret = Array.CreateInstance(ctype, jtarr.Length); + var jtarrIdx = 0; + foreach (var a in jtarr) + { + var t2 = YieldJToken(ctype, a, rank - 1); + ret.SetValue(t2, jtarrIdx++); + } + return ret; + } + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + int rank = objectType.IsArray ? objectType.GetArrayRank() : 0; + Type ctype = objectType.IsArray ? objectType.GetElementType() : objectType; + + var ret = YieldJToken(ctype, JToken.Load(reader), rank); + if (ret != null && rank > 0) return ret; + return ret; + } + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + Type objectType = value.GetType(); + if (objectType.IsArray) + { + int rank = objectType.GetArrayRank(); + int[] indices = new int[rank]; + GetJObject(value as Array, indices, 0).WriteTo(writer); + } + else + GetJObject(value).WriteTo(writer); + } + public static JToken GetJObject(object value) + { + if (value is BitArray) return JToken.FromObject((value as BitArray)?.To1010()); + if (value is IPAddress) return JToken.FromObject((value as IPAddress)?.ToString()); + if (value is ValueTuple || value is ValueTuple?) + { + ValueTuple? cidrValue = (ValueTuple?)value; + return JToken.FromObject(cidrValue == null ? null : $"{cidrValue.Value.Item1.ToString()}/{cidrValue.Value.Item2.ToString()}"); + } + return JToken.FromObject(value?.ToString()); + } + public static JToken GetJObject(Array value, int[] indices, int idx) + { + if (idx == indices.Length) + { + return GetJObject(value.GetValue(indices)); + } + JArray ja = new JArray(); + if (indices.Length == 1) + { + foreach (object a in value) + ja.Add(GetJObject(a)); + return ja; + } + int lb = value.GetLowerBound(idx); + int ub = value.GetUpperBound(idx); + for (int b = lb; b <= ub; b++) + { + indices[idx] = b; + ja.Add(GetJObject(value, indices, idx + 1)); + } + return ja; + } + } +} \ No newline at end of file diff --git a/Providers/FreeSql.Provider.QuestDb/QuestDbAdo/QuestDbTypesExtensions.cs b/Providers/FreeSql.Provider.QuestDb/QuestDbAdo/QuestDbTypesExtensions.cs new file mode 100644 index 00000000..909224ee --- /dev/null +++ b/Providers/FreeSql.Provider.QuestDb/QuestDbAdo/QuestDbTypesExtensions.cs @@ -0,0 +1,73 @@ +using NpgsqlTypes; +using System; +using System.Collections; + +public static partial class QuestDbTypesExtensions +{ + /// + /// 测量两个经纬度的距离,返回单位:米 + /// + /// 经纬坐标1 + /// 经纬坐标2 + /// 返回距离(单位:米) + public static double Distance(this NpgsqlPoint that, NpgsqlPoint point) + { + double radLat1 = (double)(that.Y) * Math.PI / 180d; + double radLng1 = (double)(that.X) * Math.PI / 180d; + double radLat2 = (double)(point.Y) * Math.PI / 180d; + double radLng2 = (double)(point.X) * Math.PI / 180d; + return 2 * Math.Asin(Math.Sqrt(Math.Pow(Math.Sin((radLat1 - radLat2) / 2), 2) + Math.Cos(radLat1) * Math.Cos(radLat2) * Math.Pow(Math.Sin((radLng1 - radLng2) / 2), 2))) * 6378137; + } + +#if net40 +#else + /// + /// 测量两个经纬度的距离,返回单位:米 + /// + /// 经纬坐标1 + /// 经纬坐标2 + /// 返回距离(单位:米) + public static double Distance(this Npgsql.LegacyPostgis.PostgisPoint that, Npgsql.LegacyPostgis.PostgisPoint point) + { + double radLat1 = (double)(that.Y) * Math.PI / 180d; + double radLng1 = (double)(that.X) * Math.PI / 180d; + double radLat2 = (double)(point.Y) * Math.PI / 180d; + double radLng2 = (double)(point.X) * Math.PI / 180d; + return 2 * Math.Asin(Math.Sqrt(Math.Pow(Math.Sin((radLat1 - radLat2) / 2), 2) + Math.Cos(radLat1) * Math.Cos(radLat2) * Math.Pow(Math.Sin((radLng1 - radLng2) / 2), 2))) * 6378137; + } + + public static NpgsqlRange ToNpgsqlRange(this string that) + { + var s = that; + if (string.IsNullOrEmpty(s) || s == "empty") return NpgsqlRange.Empty; + string s1 = s.Trim('(', ')', '[', ']'); + string[] ss = s1.Split(new char[] { ',' }, 2); + if (ss.Length != 2) return NpgsqlRange.Empty; + T t1 = default(T); + T t2 = default(T); + if (!string.IsNullOrEmpty(ss[0])) t1 = (T)Convert.ChangeType(ss[0], typeof(T)); + if (!string.IsNullOrEmpty(ss[1])) t2 = (T)Convert.ChangeType(ss[1], typeof(T)); + return new NpgsqlRange(t1, s[0] == '[', s[0] == '(', t2, s[s.Length - 1] == ']', s[s.Length - 1] == ')'); + } +#endif + + public static string To1010(this BitArray ba) + { + char[] ret = new char[ba.Length]; + for (int a = 0; a < ba.Length; a++) ret[a] = ba[a] ? '1' : '0'; + return new string(ret); + } + + /// + /// 将 1010101010 这样的二进制字符串转换成 BitArray + /// + /// 1010101010 + /// + public static BitArray ToBitArray(this string _1010Str) + { + if (_1010Str == null) return null; + BitArray ret = new BitArray(_1010Str.Length); + for (int a = 0; a < _1010Str.Length; a++) ret[a] = _1010Str[a] == '1'; + return ret; + } +} \ No newline at end of file diff --git a/Providers/FreeSql.Provider.QuestDb/QuestDbCodeFirst.cs b/Providers/FreeSql.Provider.QuestDb/QuestDbCodeFirst.cs new file mode 100644 index 00000000..501e075c --- /dev/null +++ b/Providers/FreeSql.Provider.QuestDb/QuestDbCodeFirst.cs @@ -0,0 +1,278 @@ +using FreeSql.Internal; +using FreeSql.Internal.Model; +using FreeSql.Provider.QuestDb.Subtable; +using Newtonsoft.Json.Linq; +using Npgsql.LegacyPostgis; +using NpgsqlTypes; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Numerics; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; + +namespace FreeSql.QuestDb +{ + class QuestDbCodeFirst : Internal.CommonProvider.CodeFirstProvider + { + public QuestDbCodeFirst(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression) : base(orm, + commonUtils, commonExpression) + { + } + + static object _dicCsToDbLock = new object(); + + static Dictionary> _dicCsToDb = new Dictionary>() + { + { typeof(sbyte).FullName, CsToDb.New(NpgsqlDbType.Smallint, "byte", "byte NOT NULL", false, false, 0) }, + { typeof(sbyte?).FullName, CsToDb.New(NpgsqlDbType.Smallint, "byte", "byte", false, true, null) }, + { typeof(short).FullName, CsToDb.New(NpgsqlDbType.Smallint, "short", "short NOT NULL", false, false, 0) }, + { typeof(short?).FullName, CsToDb.New(NpgsqlDbType.Smallint, "short", "short", false, true, null) }, + { typeof(int).FullName, CsToDb.New(NpgsqlDbType.Integer, "int", "int NOT NULL", false, false, 0) }, + { typeof(int?).FullName, CsToDb.New(NpgsqlDbType.Integer, "int", "int", false, true, null) }, + { typeof(long).FullName, CsToDb.New(NpgsqlDbType.Bigint, "long", "long NOT NULL", false, false, 0) }, + { typeof(long?).FullName, CsToDb.New(NpgsqlDbType.Bigint, "long", "long", false, true, null) }, + + { typeof(byte).FullName, CsToDb.New(NpgsqlDbType.Smallint, "byte", "byte NOT NULL", false, false, 0) }, + { typeof(byte?).FullName, CsToDb.New(NpgsqlDbType.Smallint, "byte", "byte", false, true, null) }, + { typeof(ushort).FullName, CsToDb.New(NpgsqlDbType.Integer, "short", "short NOT NULL", false, false, 0) }, + { typeof(ushort?).FullName, CsToDb.New(NpgsqlDbType.Integer, "short", "short", false, true, null) }, + { typeof(uint).FullName, CsToDb.New(NpgsqlDbType.Bigint, "int", "int NOT NULL", false, false, 0) }, + { typeof(uint?).FullName, CsToDb.New(NpgsqlDbType.Bigint, "int", "int", false, true, null) }, + { + typeof(ulong).FullName, CsToDb.New(NpgsqlDbType.Numeric, "long", "long NOT NULL", false, false, 0) + }, + { + typeof(ulong?).FullName, CsToDb.New(NpgsqlDbType.Numeric, "long", "long", false, true, null) + }, + + { typeof(float).FullName, CsToDb.New(NpgsqlDbType.Real, "float", "float NOT NULL", false, false, 0) }, + { typeof(float?).FullName, CsToDb.New(NpgsqlDbType.Real, "float", "float", false, true, null) }, + { typeof(double).FullName, CsToDb.New(NpgsqlDbType.Double, "double", "double NOT NULL", false, false, 0) }, + { typeof(double?).FullName, CsToDb.New(NpgsqlDbType.Double, "double", "double", false, true, null) }, + { + typeof(decimal).FullName, + CsToDb.New(NpgsqlDbType.Numeric, "double", "double NOT NULL", false, false, 0) + }, + { + typeof(decimal?).FullName, + CsToDb.New(NpgsqlDbType.Numeric, "double", "double", false, true, null) + }, + + { typeof(string).FullName, CsToDb.New(NpgsqlDbType.Varchar, "string", "string", false, null, "") }, + { typeof(char).FullName, CsToDb.New(NpgsqlDbType.Char, "char", "char)", false, null, '\0') }, + + { + typeof(TimeSpan).FullName, + CsToDb.New(NpgsqlDbType.Time, "timestamp", "timestamp NOT NULL", false, false, 0) + }, + { typeof(TimeSpan?).FullName, CsToDb.New(NpgsqlDbType.Time, "timestamp", "timestamp", false, true, null) }, + { + typeof(DateTime).FullName, + CsToDb.New(NpgsqlDbType.Timestamp, "timestamp", "timestamp NOT NULL", false, false, + new DateTime(1970, 1, 1)) + }, + { + typeof(DateTime?).FullName, + CsToDb.New(NpgsqlDbType.Timestamp, "timestamp", "timestamp", false, true, null) + }, + + { + typeof(bool).FullName, + CsToDb.New(NpgsqlDbType.Boolean, "boolean", "boolean NOT NULL", null, false, false) + }, + { typeof(bool?).FullName, CsToDb.New(NpgsqlDbType.Boolean, "boolean", "boolean", null, true, null) }, + //{ typeof(Byte[]).FullName, CsToDb.New(NpgsqlDbType.Bytea, "bytea", "bytea", false, null, new byte[0]) }, + //{ + // typeof(BitArray).FullName, + // CsToDb.New(NpgsqlDbType.Varbit, "varbit", "varbit(64)", false, null, new BitArray(new byte[64])) + //}, + { + typeof(BigInteger).FullName, + CsToDb.New(NpgsqlDbType.Numeric, "long", "long NOT NULL", false, false, 0) + }, + { + typeof(BigInteger?).FullName, + CsToDb.New(NpgsqlDbType.Numeric, "long", "long", false, true, null) + } + }; + + public override DbInfoResult GetDbInfo(Type type) + { + if (_dicCsToDb.TryGetValue(type.FullName, out var trydc)) + return new DbInfoResult((int)trydc.type, trydc.dbtype, trydc.dbtypeFull, trydc.isnullable, + trydc.defaultValue); + if (type.IsArray) + return null; + return null; + } + + protected override string GetComparisonDDLStatements(params TypeAndName[] objects) + { + var sb = new StringBuilder(); + var seqcols = new List>(); //序列 + + 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(CoreStrings.S_Type_IsNot_Migrable(obj.entityType.FullName)); + if (tb.Columns.Any() == false) + throw new Exception(CoreStrings.S_Type_IsNot_Migrable_0Attributes(obj.entityType.FullName)); + var tbnameArray = _commonUtils.SplitTableName(tb.DbName); + var tbname = string.Empty; + if (tbnameArray?.Length == 1) tbname = tbnameArray.FirstOrDefault(); + + var tboldnameArray = _commonUtils.SplitTableName(tb.DbOldName); + var tboldname = string.Empty; + if (tboldnameArray?.Length == 1) + tboldname = tboldnameArray.FirstOrDefault(); + + var sbalter = new StringBuilder(); + var allTable = _orm.Ado.Query(CommandType.Text, + @"SHOW TABLES"); + //如果旧表名和现表名均不存在,则直接创建表 + if (string.IsNullOrWhiteSpace(tboldname) && allTable.Any(s => s.Equals(tbname)) == false) + { + //创建表 + var createTableName = _commonUtils.QuoteSqlName(tbname); + sbalter.Append("CREATE TABLE ").Append(createTableName).Append(" ( "); + foreach (var tbcol in tb.ColumnsByPosition) + { + sbalter.Append(" \r\n ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(" ") + .Append(tbcol.Attribute.DbType); + if (tbcol.Attribute.IsIdentity == true) + { + //No IsIdentity + } + + sbalter.Append(","); + } + + sbalter.Remove(sbalter.Length - 1, 1); + sbalter.Append(") "); + if (tb.Indexes.Any()) + { + sbalter.Append(",\r\n"); + } + + //创建表的索引 + foreach (var uk in tb.Indexes) + { + sbalter.Append($"INDEX ("); + foreach (var tbcol in uk.Columns) + { + if (tbcol.Column.Attribute.DbType != "SYMBOL") + throw new Exception("索引只能是string类型且[Column(DbType = \"symbol\")]"); + sbalter.Append($"{_commonUtils.QuoteSqlName(tbcol.Column.Attribute.Name)}"); + } + + sbalter.Append($"),"); + } + + sbalter.Remove(sbalter.Length - 1, 1); + //是否存在分表 + foreach (var propety in obj.entityType.GetProperties()) + { + var timeAttr = propety.GetCustomAttribute(); + if (timeAttr != null) + { + var colName = tb.Columns.FirstOrDefault(it => it.Key == propety.Name).Value; + sbalter.Append( + $" TIMESTAMP({colName.Attribute.Name}) PARTITION BY {timeAttr.SubtableType};{Environment.NewLine}"); + } + } + } + //如果旧表名特性存在,旧表名在数据库中存在,现表名在数据库中不存在,直接走修改表名逻辑 + else if (string.IsNullOrWhiteSpace(tboldname) == false && allTable.Any(s => s.Equals(tboldname)) && + allTable.Any(s => s.Equals(tbname)) == false) + { + //修改表名 + sbalter.Append("RENAME TABLE ") + .Append(_commonUtils.QuoteSqlName(tboldname)) + .Append(" TO ").Append(_commonUtils.QuoteSqlName(tbname)) + .Append($";{Environment.NewLine}"); + } + //如果旧表名特性存在,旧表名在数据库中不存在,现表名在数据库中存在,对比列 + //如果旧表名特性不存在 现表名在数据库中存在,对比列 + else if ((string.IsNullOrWhiteSpace(tboldname) == false && + allTable.Any(s => s.Equals(tboldname)) == false && + allTable.Any(s => s.Equals(tbname)) == true) + || (string.IsNullOrWhiteSpace(tboldname) == true && + allTable.Any(s => s.Equals(tbname)) == true)) + + { + //查询列 + var questDbColumnInfo = _orm.Ado.ExecuteArray($"SHOW COLUMNS FROM '{tbname}'").Select(o => new + { + columnName = o[0].ToString(), + indexed = Convert.ToBoolean(o[2]) + }).ToList(); + //对比列 + foreach (var tbcol in tb.ColumnsByPosition) + { + //如果旧列名存在 现列名均不存在 直接添加列 + if (questDbColumnInfo.Any(a => a.columnName.Equals(tbcol.Attribute.OldName)) == false + && questDbColumnInfo.Any(a => a.columnName.Equals(tbcol.Attribute.Name)) == + false) + { + sbalter.Append("ALTER TABLE ").Append(tbname) + .Append(" ADD COLUMN ").Append(tbcol.Attribute.Name).Append(" ") + .Append(tbcol.Attribute.DbType).Append($";{Environment.NewLine}"); + questDbColumnInfo.Add(new + { + columnName = tbcol.Attribute.Name, + indexed = false + }); + } + //如果旧列名存在,现列名不存在,直接修改列名 + else if (questDbColumnInfo.Any(a => + a.columnName.ToString().Equals(tbcol.Attribute.OldName)) == true + && questDbColumnInfo.Any(a => a.columnName.ToString().Equals(tbcol.Attribute.Name)) == + false) + { + sbalter.Append("ALTER TABLE ").Append(tbname) + .Append(" RENAME COLUMN ").Append(tbcol.Attribute.OldName).Append(" TO ") + .Append(tbcol.Attribute.Name).Append($";{Environment.NewLine}"); + } + } + + //对比索引 + foreach (var uk in tb.Indexes) + { + if (string.IsNullOrEmpty(uk.Name) || uk.Columns.Any() == false) + continue; + var ukname = ReplaceIndexName(uk.Name, tbname); + //先判断表中有没此字段的索引 + var isIndex = questDbColumnInfo + .Where(a => a.columnName.ToString().Equals(uk.Columns.First().Column.Attribute.Name)) + .FirstOrDefault()?.indexed; + //如果此字段不是索引 + if (isIndex != null && isIndex == false) + { + //创建索引 + sbalter.Append($"ALTER TABLE {tbname} ALTER COLUMN "); + foreach (var tbcol in uk.Columns) + { + if (tbcol.Column.Attribute.DbType != "SYMBOL") + throw new Exception("索引只能是string类型且[Column(DbType = \"symbol\")]"); + sbalter.Append($"{tbcol.Column.Attribute.Name}"); + } + + sbalter.Append($" ADD INDEX;{Environment.NewLine}"); + } + } + } + + sb.Append(sbalter); + } + + return sb.Length == 0 ? null : sb.ToString(); + } + } +} \ No newline at end of file diff --git a/Providers/FreeSql.Provider.QuestDb/QuestDbDbFirst.cs b/Providers/FreeSql.Provider.QuestDb/QuestDbDbFirst.cs new file mode 100644 index 00000000..8c558462 --- /dev/null +++ b/Providers/FreeSql.Provider.QuestDb/QuestDbDbFirst.cs @@ -0,0 +1,783 @@ +using FreeSql.DatabaseModel; +using FreeSql.Internal; +using FreeSql.Internal.Model; +using Newtonsoft.Json.Linq; +using Npgsql.LegacyPostgis; +using NpgsqlTypes; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Text; +using System.Text.RegularExpressions; + +namespace FreeSql.QuestDb +{ + class QuestDbDbFirst : IDbFirst + { + IFreeSql _orm; + protected CommonUtils _commonUtils; + protected CommonExpression _commonExpression; + + public QuestDbDbFirst(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression) + { + _orm = orm; + _commonUtils = commonUtils; + _commonExpression = commonExpression; + } + + public bool IsPg10 => ServerVersion >= 10; + + public int ServerVersion + { + get + { + if (_ServerVersionValue == 0 && _orm.Ado.MasterPool != null) + using (var conn = _orm.Ado.MasterPool.Get()) + { + try + { + _ServerVersionValue = ParsePgVersion(conn.Value.ServerVersion, 10, 0).Item2; + } + catch + { + _ServerVersionValue = 9; + } + } + + return _ServerVersionValue; + } + } + + int _ServerVersionValue = 0; + + public int GetDbType(DbColumnInfo column) => (int)GetNpgsqlDbType(column); + + NpgsqlDbType GetNpgsqlDbType(DbColumnInfo column) + { + var dbtype = column.DbTypeText; + var isarray = dbtype?.EndsWith("[]") == true; + if (isarray) dbtype = dbtype.Remove(dbtype.Length - 2); + NpgsqlDbType ret = NpgsqlDbType.Unknown; + switch (dbtype?.ToLower().TrimStart('_')) + { + case "short": + ret = NpgsqlDbType.Smallint; + break; + case "int": + ret = NpgsqlDbType.Integer; + break; + case "long": + ret = NpgsqlDbType.Bigint; + break; + case "numeric": + ret = NpgsqlDbType.Numeric; + break; + case "float": + ret = NpgsqlDbType.Real; + break; + case "double": + ret = NpgsqlDbType.Double; + break; + case "char": + ret = NpgsqlDbType.Char; + break; + case "string": + ret = NpgsqlDbType.Varchar; + break; + case "timestamp": + ret = NpgsqlDbType.Timestamp; + break; + case "timestamptz": + ret = NpgsqlDbType.TimestampTz; + break; + case "date": + ret = NpgsqlDbType.Date; + break; + case "time": + ret = NpgsqlDbType.Time; + break; + case "timetz": + ret = NpgsqlDbType.TimeTz; + break; + case "interval": + ret = NpgsqlDbType.Interval; + break; + + case "bool": + ret = NpgsqlDbType.Boolean; + break; + case "bytea": + ret = NpgsqlDbType.Bytea; + break; + case "bit": + ret = NpgsqlDbType.Bit; + break; + case "varbit": + ret = NpgsqlDbType.Varbit; + break; + + case "point": + ret = NpgsqlDbType.Point; + break; + case "line": + ret = NpgsqlDbType.Line; + break; + case "lseg": + ret = NpgsqlDbType.LSeg; + break; + case "box": + ret = NpgsqlDbType.Box; + break; + case "path": + ret = NpgsqlDbType.Path; + break; + case "polygon": + ret = NpgsqlDbType.Polygon; + break; + case "circle": + ret = NpgsqlDbType.Circle; + break; + + case "cidr": + ret = NpgsqlDbType.Cidr; + break; + case "inet": + ret = NpgsqlDbType.Inet; + break; + case "macaddr": + ret = NpgsqlDbType.MacAddr; + break; + + case "json": + ret = NpgsqlDbType.Json; + break; + case "jsonb": + ret = NpgsqlDbType.Jsonb; + break; + case "uuid": + ret = NpgsqlDbType.Uuid; + break; + + case "int4range": + ret = NpgsqlDbType.Range | NpgsqlDbType.Integer; + break; + case "int8range": + ret = NpgsqlDbType.Range | NpgsqlDbType.Bigint; + break; + case "numrange": + ret = NpgsqlDbType.Range | NpgsqlDbType.Numeric; + break; + case "tsrange": + ret = NpgsqlDbType.Range | NpgsqlDbType.Timestamp; + break; + case "tstzrange": + ret = NpgsqlDbType.Range | NpgsqlDbType.TimestampTz; + break; + case "daterange": + ret = NpgsqlDbType.Range | NpgsqlDbType.Date; + break; + + case "hstore": + ret = NpgsqlDbType.Hstore; + break; + case "geometry": + ret = NpgsqlDbType.Geometry; + break; + } + + return isarray ? (ret | NpgsqlDbType.Array) : ret; + } + + static readonly + Dictionary _dicDbToCs = + new Dictionary() + { + { + (int)NpgsqlDbType.Smallint, + ("(short?)", "short.Parse({0})", "{0}.ToString()", "short?", typeof(short), typeof(short?), + "{0}.Value", "GetInt16") + }, + { + (int)NpgsqlDbType.Integer, + ("(int?)", "int.Parse({0})", "{0}.ToString()", "int?", typeof(int), typeof(int?), "{0}.Value", + "GetInt32") + }, + { + (int)NpgsqlDbType.Bigint, + ("(long?)", "long.Parse({0})", "{0}.ToString()", "long?", typeof(long), typeof(long?), + "{0}.Value", "GetInt64") + }, + { + (int)NpgsqlDbType.Numeric, + ("(decimal?)", "decimal.Parse({0})", "{0}.ToString()", "decimal?", typeof(decimal), + typeof(decimal?), "{0}.Value", "GetDecimal") + }, + { + (int)NpgsqlDbType.Real, + ("(float?)", "float.Parse({0})", "{0}.ToString()", "float?", typeof(float), typeof(float?), + "{0}.Value", "GetFloat") + }, + { + (int)NpgsqlDbType.Double, + ("(double?)", "double.Parse({0})", "{0}.ToString()", "double?", typeof(double), typeof(double?), + "{0}.Value", "GetDouble") + }, + { + (int)NpgsqlDbType.Money, + ("(decimal?)", "decimal.Parse({0})", "{0}.ToString()", "decimal?", typeof(decimal), + typeof(decimal?), "{0}.Value", "GetDecimal") + }, + + { + (int)NpgsqlDbType.Char, + ("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", + typeof(string), typeof(string), "{0}", "GetString") + }, + { + (int)NpgsqlDbType.Varchar, + ("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", + typeof(string), typeof(string), "{0}", "GetString") + }, + { + (int)NpgsqlDbType.Text, + ("", "{0}.Replace(StringifySplit, \"|\")", "{0}.Replace(\"|\", StringifySplit)", "string", + typeof(string), typeof(string), "{0}", "GetString") + }, + + { + (int)NpgsqlDbType.Timestamp, + ("(DateTime?)", "new DateTime(long.Parse({0}))", "{0}.Ticks.ToString()", "DateTime?", + typeof(DateTime), typeof(DateTime?), "{0}.Value", "GetDateTime") + }, + { + (int)NpgsqlDbType.TimestampTz, + ("(DateTime?)", "new DateTime(long.Parse({0}))", "{0}.Ticks.ToString()", "DateTime?", + typeof(DateTime), typeof(DateTime?), "{0}.Value", "GetDateTime") + }, + { + (int)NpgsqlDbType.Date, + ("(DateTime?)", "new DateTime(long.Parse({0}))", "{0}.Ticks.ToString()", "DateTime?", + typeof(DateTime), typeof(DateTime?), "{0}.Value", "GetDateTime") + }, + { + (int)NpgsqlDbType.Time, + ("(TimeSpan?)", "TimeSpan.Parse(double.Parse({0}))", "{0}.Ticks.ToString()", "TimeSpan?", + typeof(TimeSpan), typeof(TimeSpan?), "{0}.Value", "GetValue") + }, + { + (int)NpgsqlDbType.TimeTz, + ("(TimeSpan?)", "TimeSpan.Parse(double.Parse({0}))", "{0}.Ticks.ToString()", "TimeSpan?", + typeof(TimeSpan), typeof(TimeSpan?), "{0}.Value", "GetValue") + }, + { + (int)NpgsqlDbType.Interval, + ("(TimeSpan?)", "TimeSpan.Parse(double.Parse({0}))", "{0}.Ticks.ToString()", "TimeSpan?", + typeof(TimeSpan), typeof(TimeSpan?), "{0}.Value", "GetValue") + }, + + { + (int)NpgsqlDbType.Boolean, + ("(bool?)", "{0} == \"1\"", "{0} == true ? \"1\" : \"0\"", "bool?", typeof(bool), typeof(bool?), + "{0}.Value", "GetBoolean") + }, + { + (int)NpgsqlDbType.Bytea, + ("(byte[])", "Convert.FromBase64String({0})", "Convert.ToBase64String({0})", "byte[]", + typeof(byte[]), typeof(byte[]), "{0}", "GetValue") + }, + { + (int)NpgsqlDbType.Bit, + ("(BitArray)", "{0}.ToBitArray()", "{0}.To1010()", "BitArray", typeof(BitArray), + typeof(BitArray), "{0}", "GetValue") + }, + { + (int)NpgsqlDbType.Varbit, + ("(BitArray)", "{0}.ToBitArray()", "{0}.To1010()", "BitArray", typeof(BitArray), + typeof(BitArray), "{0}", "GetValue") + }, + + { + (int)NpgsqlDbType.Point, + ("(NpgsqlPoint?)", "NpgsqlPoint.Parse({0})", "{0}.ToString()", "NpgsqlPoint", + typeof(NpgsqlPoint), typeof(NpgsqlPoint?), "{0}", "GetValue") + }, + { + (int)NpgsqlDbType.Line, + ("(NpgsqlLine?)", "NpgsqlLine.Parse({0})", "{0}.ToString()", "NpgsqlLine", typeof(NpgsqlLine), + typeof(NpgsqlLine?), "{0}", "GetValue") + }, + { + (int)NpgsqlDbType.LSeg, + ("(NpgsqlLSeg?)", "NpgsqlLSeg.Parse({0})", "{0}.ToString()", "NpgsqlLSeg", typeof(NpgsqlLSeg), + typeof(NpgsqlLSeg?), "{0}", "GetValue") + }, + { + (int)NpgsqlDbType.Box, + ("(NpgsqlBox?)", "NpgsqlBox.Parse({0})", "{0}.ToString()", "NpgsqlBox", typeof(NpgsqlBox), + typeof(NpgsqlBox?), "{0}", "GetValue") + }, + { + (int)NpgsqlDbType.Path, + ("(NpgsqlPath?)", "NpgsqlPath.Parse({0})", "{0}.ToString()", "NpgsqlPath", typeof(NpgsqlPath), + typeof(NpgsqlPath?), "{0}", "GetValue") + }, + { + (int)NpgsqlDbType.Polygon, + ("(NpgsqlPolygon?)", "NpgsqlPolygon.Parse({0})", "{0}.ToString()", "NpgsqlPolygon", + typeof(NpgsqlPolygon), typeof(NpgsqlPolygon?), "{0}", "GetValue") + }, + { + (int)NpgsqlDbType.Circle, + ("(NpgsqlCircle?)", "NpgsqlCircle.Parse({0})", "{0}.ToString()", "NpgsqlCircle", + typeof(NpgsqlCircle), typeof(NpgsqlCircle?), "{0}", "GetValue") + }, + + { + (int)NpgsqlDbType.Cidr, + ("((IPAddress, int)?)", "(IPAddress, int)({0})", "{0}.ToString()", "(IPAddress, int)", + typeof((IPAddress, int)), typeof((IPAddress, int)?), "{0}", "GetValue") + }, + { + (int)NpgsqlDbType.Inet, + ("(IPAddress)", "IPAddress.Parse({0})", "{0}.ToString()", "IPAddress", typeof(IPAddress), + typeof(IPAddress), "{0}", "GetValue") + }, + { + (int)NpgsqlDbType.MacAddr, + ("(PhysicalAddress?)", "PhysicalAddress.Parse({0})", "{0}.ToString()", "PhysicalAddress", + typeof(PhysicalAddress), typeof(PhysicalAddress), "{0}", "GetValue") + }, + + { + (int)NpgsqlDbType.Json, + ("(JToken)", "JToken.Parse({0})", "{0}.ToString()", "JToken", typeof(JToken), typeof(JToken), + "{0}", "GetString") + }, + { + (int)NpgsqlDbType.Jsonb, + ("(JToken)", "JToken.Parse({0})", "{0}.ToString()", "JToken", typeof(JToken), typeof(JToken), + "{0}", "GetString") + }, + { + (int)NpgsqlDbType.Uuid, + ("(Guid?)", "Guid.Parse({0})", "{0}.ToString()", "Guid", typeof(Guid), typeof(Guid?), "{0}", + "GetString") + }, + + { + (int)(NpgsqlDbType.Range | NpgsqlDbType.Integer), + ("(NpgsqlRange?)", "{0}.ToNpgsqlRange()", "{0}.ToString()", "NpgsqlRange", + typeof(NpgsqlRange), typeof(NpgsqlRange?), "{0}", "GetString") + }, + { + (int)(NpgsqlDbType.Range | NpgsqlDbType.Bigint), + ("(NpgsqlRange?)", "{0}.ToNpgsqlRange()", "{0}.ToString()", "NpgsqlRange", + typeof(NpgsqlRange), typeof(NpgsqlRange?), "{0}", "GetString") + }, + { + (int)(NpgsqlDbType.Range | NpgsqlDbType.Numeric), + ("(NpgsqlRange?)", "{0}.ToNpgsqlRange()", "{0}.ToString()", + "NpgsqlRange", typeof(NpgsqlRange), typeof(NpgsqlRange?), "{0}", + "GetString") + }, + { + (int)(NpgsqlDbType.Range | NpgsqlDbType.Timestamp), + ("(NpgsqlRange?)", "{0}.ToNpgsqlRange()", "{0}.ToString()", + "NpgsqlRange", typeof(NpgsqlRange), typeof(NpgsqlRange?), + "{0}", "GetString") + }, + { + (int)(NpgsqlDbType.Range | NpgsqlDbType.TimestampTz), + ("(NpgsqlRange?)", "{0}.ToNpgsqlRange()", "{0}.ToString()", + "NpgsqlRange", typeof(NpgsqlRange), typeof(NpgsqlRange?), + "{0}", "GetString") + }, + { + (int)(NpgsqlDbType.Range | NpgsqlDbType.Date), + ("(NpgsqlRange?)", "{0}.ToNpgsqlRange()", "{0}.ToString()", + "NpgsqlRange", typeof(NpgsqlRange), typeof(NpgsqlRange?), + "{0}", "GetString") + }, + + { + (int)NpgsqlDbType.Hstore, + ("(Dictionary)", + "JsonConvert.DeserializeObject>({0})", + "JsonConvert.SerializeObject({0})", "Dictionary", + typeof(Dictionary), typeof(Dictionary), "{0}", "GetValue") + }, + { + (int)NpgsqlDbType.Geometry, + ("(PostgisGeometry)", "JsonConvert.DeserializeObject({0})", + "JsonConvert.SerializeObject({0})", "PostgisGeometry", typeof(PostgisGeometry), + typeof(PostgisGeometry), "{0}", "GetValue") + }, + + /*** array ***/ + + { + (int)(NpgsqlDbType.Smallint | NpgsqlDbType.Array), + ("(short[])", "JsonConvert.DeserializeObject({0})", "JsonConvert.SerializeObject({0})", + "short[]", typeof(short[]), typeof(short[]), "{0}", "GetValue") + }, + { + (int)(NpgsqlDbType.Integer | NpgsqlDbType.Array), + ("(int[])", "JsonConvert.DeserializeObject({0})", "JsonConvert.SerializeObject({0})", + "int[]", typeof(int[]), typeof(int[]), "{0}", "GetValue") + }, + { + (int)(NpgsqlDbType.Bigint | NpgsqlDbType.Array), + ("(long[])", "JsonConvert.DeserializeObject({0})", "JsonConvert.SerializeObject({0})", + "long[]", typeof(long[]), typeof(long[]), "{0}", "GetValue") + }, + { + (int)(NpgsqlDbType.Numeric | NpgsqlDbType.Array), + ("(decimal[])", "JsonConvert.DeserializeObject({0})", + "JsonConvert.SerializeObject({0})", "decimal[]", typeof(decimal[]), typeof(decimal[]), + "{0}", "GetValue") + }, + { + (int)(NpgsqlDbType.Real | NpgsqlDbType.Array), + ("(float[])", "JsonConvert.DeserializeObject({0})", "JsonConvert.SerializeObject({0})", + "float[]", typeof(float[]), typeof(float[]), "{0}", "GetValue") + }, + { + (int)(NpgsqlDbType.Double | NpgsqlDbType.Array), + ("(double[])", "JsonConvert.DeserializeObject({0})", + "JsonConvert.SerializeObject({0})", "double[]", typeof(double[]), typeof(double[]), "{0}", + "GetValue") + }, + { + (int)(NpgsqlDbType.Money | NpgsqlDbType.Array), + ("(decimal[])", "JsonConvert.DeserializeObject({0})", + "JsonConvert.SerializeObject({0})", "decimal[]", typeof(decimal[]), typeof(decimal[]), + "{0}", "GetValue") + }, + + { + (int)(NpgsqlDbType.Char | NpgsqlDbType.Array), + ("(string[])", "JsonConvert.DeserializeObject({0})", + "JsonConvert.SerializeObject({0})", "string[]", typeof(string[]), typeof(string[]), "{0}", + "GetValue") + }, + { + (int)(NpgsqlDbType.Varchar | NpgsqlDbType.Array), + ("(string[])", "JsonConvert.DeserializeObject({0})", + "JsonConvert.SerializeObject({0})", "string[]", typeof(string[]), typeof(string[]), "{0}", + "GetValue") + }, + { + (int)(NpgsqlDbType.Text | NpgsqlDbType.Array), + ("(string[])", "JsonConvert.DeserializeObject({0})", + "JsonConvert.SerializeObject({0})", "string[]", typeof(string[]), typeof(string[]), "{0}", + "GetValue") + }, + + { + (int)(NpgsqlDbType.Timestamp | NpgsqlDbType.Array), + ("(DateTime[])", "JsonConvert.DeserializeObject({0})", + "JsonConvert.SerializeObject({0})", "DateTime[]", typeof(DateTime[]), typeof(DateTime[]), + "{0}", "GetValue") + }, + { + (int)(NpgsqlDbType.TimestampTz | NpgsqlDbType.Array), + ("(DateTime[])", "JsonConvert.DeserializeObject({0})", + "JsonConvert.SerializeObject({0})", "DateTime[]", typeof(DateTime[]), typeof(DateTime[]), + "{0}", "GetValue") + }, + { + (int)(NpgsqlDbType.Date | NpgsqlDbType.Array), + ("(DateTime[])", "JsonConvert.DeserializeObject({0})", + "JsonConvert.SerializeObject({0})", "DateTime[]", typeof(DateTime[]), typeof(DateTime[]), + "{0}", "GetValue") + }, + { + (int)(NpgsqlDbType.Time | NpgsqlDbType.Array), + ("(TimeSpan[])", "JsonConvert.DeserializeObject({0})", + "JsonConvert.SerializeObject({0})", "TimeSpan[]", typeof(TimeSpan[]), typeof(TimeSpan[]), + "{0}", "GetValue") + }, + { + (int)(NpgsqlDbType.TimeTz | NpgsqlDbType.Array), + ("(TimeSpan[])", "JsonConvert.DeserializeObject({0})", + "JsonConvert.SerializeObject({0})", "TimeSpan[]", typeof(TimeSpan[]), typeof(TimeSpan[]), + "{0}", "GetValue") + }, + { + (int)(NpgsqlDbType.Interval | NpgsqlDbType.Array), + ("(TimeSpan[])", "JsonConvert.DeserializeObject({0})", + "JsonConvert.SerializeObject({0})", "TimeSpan[]", typeof(TimeSpan[]), typeof(TimeSpan[]), + "{0}", "GetValue") + }, + + { + (int)(NpgsqlDbType.Boolean | NpgsqlDbType.Array), + ("(bool[])", "JsonConvert.DeserializeObject({0})", "JsonConvert.SerializeObject({0})", + "bool[]", typeof(bool[]), typeof(bool[]), "{0}", "GetValue") + }, + { + (int)(NpgsqlDbType.Bytea | NpgsqlDbType.Array), + ("(byte[][])", "JsonConvert.DeserializeObject({0})", + "JsonConvert.SerializeObject({0})", "byte[][]", typeof(byte[][]), typeof(byte[][]), "{0}", + "GetValue") + }, + { + (int)(NpgsqlDbType.Bit | NpgsqlDbType.Array), + ("(BitArray[])", "JsonConvert.DeserializeObject({0})", + "JsonConvert.SerializeObject({0})", "BitArray[]", typeof(BitArray[]), typeof(BitArray[]), + "{0}", "GetValue") + }, + { + (int)(NpgsqlDbType.Varbit | NpgsqlDbType.Array), + ("(BitArray[])", "JsonConvert.DeserializeObject({0})", + "JsonConvert.SerializeObject({0})", "BitArray[]", typeof(BitArray[]), typeof(BitArray[]), + "{0}", "GetValue") + }, + + { + (int)(NpgsqlDbType.Point | NpgsqlDbType.Array), + ("(NpgsqlPoint[])", "JsonConvert.DeserializeObject({0})", + "JsonConvert.SerializeObject({0})", "NpgsqlPoint[]", typeof(NpgsqlPoint[]), + typeof(NpgsqlPoint[]), "{0}", "GetValue") + }, + { + (int)(NpgsqlDbType.Line | NpgsqlDbType.Array), + ("(NpgsqlLine[])", "JsonConvert.DeserializeObject({0})", + "JsonConvert.SerializeObject({0})", "NpgsqlLine[]", typeof(NpgsqlLine[]), + typeof(NpgsqlLine[]), "{0}", "GetValue") + }, + { + (int)(NpgsqlDbType.LSeg | NpgsqlDbType.Array), + ("(NpgsqlLSeg[])", "JsonConvert.DeserializeObject({0})", + "JsonConvert.SerializeObject({0})", "NpgsqlLSeg[]", typeof(NpgsqlLSeg[]), + typeof(NpgsqlLSeg[]), "{0}", "GetValue") + }, + { + (int)(NpgsqlDbType.Box | NpgsqlDbType.Array), + ("(NpgsqlBox[])", "JsonConvert.DeserializeObject({0})", + "JsonConvert.SerializeObject({0})", "NpgsqlBox[]", typeof(NpgsqlBox[]), typeof(NpgsqlBox[]), + "{0}", "GetValue") + }, + { + (int)(NpgsqlDbType.Path | NpgsqlDbType.Array), + ("(NpgsqlPath[])", "JsonConvert.DeserializeObject({0})", + "JsonConvert.SerializeObject({0})", "NpgsqlPath[]", typeof(NpgsqlPath[]), + typeof(NpgsqlPath[]), "{0}", "GetValue") + }, + { + (int)(NpgsqlDbType.Polygon | NpgsqlDbType.Array), + ("(NpgsqlPolygon[])", "JsonConvert.DeserializeObject({0})", + "JsonConvert.SerializeObject({0})", "NpgsqlPolygon[]", typeof(NpgsqlPolygon[]), + typeof(NpgsqlPolygon[]), "{0}", "GetValue") + }, + { + (int)(NpgsqlDbType.Circle | NpgsqlDbType.Array), + ("(NpgsqlCircle[])", "JsonConvert.DeserializeObject({0})", + "JsonConvert.SerializeObject({0})", "NpgsqlCircle[]", typeof(NpgsqlCircle[]), + typeof(NpgsqlCircle[]), "{0}", "GetValue") + }, + + { + (int)(NpgsqlDbType.Cidr | NpgsqlDbType.Array), + ("((IPAddress, int)[])", "JsonConvert.DeserializeObject<(IPAddress, int)[]>({0})", + "JsonConvert.SerializeObject({0})", "(IPAddress, int)[]", typeof((IPAddress, int)[]), + typeof((IPAddress, int)[]), "{0}", "GetValue") + }, + { + (int)(NpgsqlDbType.Inet | NpgsqlDbType.Array), + ("(IPAddress[])", "JsonConvert.DeserializeObject({0})", + "JsonConvert.SerializeObject({0})", "IPAddress[]", typeof(IPAddress[]), typeof(IPAddress[]), + "{0}", "GetValue") + }, + { + (int)(NpgsqlDbType.MacAddr | NpgsqlDbType.Array), + ("(PhysicalAddress[])", "JsonConvert.DeserializeObject({0})", + "JsonConvert.SerializeObject({0})", "PhysicalAddress[]", typeof(PhysicalAddress[]), + typeof(PhysicalAddress[]), "{0}", "GetValue") + }, + + { + (int)(NpgsqlDbType.Json | NpgsqlDbType.Array), + ("(JToken[])", "JsonConvert.DeserializeObject({0})", + "JsonConvert.SerializeObject({0})", "JToken[]", typeof(JToken[]), typeof(JToken[]), "{0}", + "GetValue") + }, + { + (int)(NpgsqlDbType.Jsonb | NpgsqlDbType.Array), + ("(JToken[])", "JsonConvert.DeserializeObject({0})", + "JsonConvert.SerializeObject({0})", "JToken[]", typeof(JToken[]), typeof(JToken[]), "{0}", + "GetValue") + }, + { + (int)(NpgsqlDbType.Uuid | NpgsqlDbType.Array), + ("(Guid[])", "JsonConvert.DeserializeObject({0})", "JsonConvert.SerializeObject({0})", + "Guid[]", typeof(Guid[]), typeof(Guid[]), "{0}", "GetValue") + }, + + { + (int)(NpgsqlDbType.Range | NpgsqlDbType.Integer | NpgsqlDbType.Array), + ("(NpgsqlRange[])", "JsonConvert.DeserializeObject[]>({0})", + "JsonConvert.SerializeObject({0})", "NpgsqlRange[]", typeof(NpgsqlRange[]), + typeof(NpgsqlRange[]), "{0}", "GetValue") + }, + { + (int)(NpgsqlDbType.Range | NpgsqlDbType.Bigint | NpgsqlDbType.Array), + ("(NpgsqlRange[])", "JsonConvert.DeserializeObject[]>({0})", + "JsonConvert.SerializeObject({0})", "NpgsqlRange[]", typeof(NpgsqlRange[]), + typeof(NpgsqlRange[]), "{0}", "GetValue") + }, + { + (int)(NpgsqlDbType.Range | NpgsqlDbType.Numeric | NpgsqlDbType.Array), + ("(NpgsqlRange[])", "JsonConvert.DeserializeObject[]>({0})", + "JsonConvert.SerializeObject({0})", "NpgsqlRange[]", + typeof(NpgsqlRange[]), typeof(NpgsqlRange[]), "{0}", "GetValue") + }, + { + (int)(NpgsqlDbType.Range | NpgsqlDbType.Timestamp | NpgsqlDbType.Array), + ("(NpgsqlRange[])", "JsonConvert.DeserializeObject[]>({0})", + "JsonConvert.SerializeObject({0})", "NpgsqlRange[]", + typeof(NpgsqlRange[]), typeof(NpgsqlRange[]), "{0}", "GetValue") + }, + { + (int)(NpgsqlDbType.Range | NpgsqlDbType.TimestampTz | NpgsqlDbType.Array), + ("(NpgsqlRange[])", "JsonConvert.DeserializeObject[]>({0})", + "JsonConvert.SerializeObject({0})", "NpgsqlRange[]", + typeof(NpgsqlRange[]), typeof(NpgsqlRange[]), "{0}", "GetValue") + }, + { + (int)(NpgsqlDbType.Range | NpgsqlDbType.Date | NpgsqlDbType.Array), + ("(NpgsqlRange[])", "JsonConvert.DeserializeObject[]>({0})", + "JsonConvert.SerializeObject({0})", "NpgsqlRange[]", + typeof(NpgsqlRange[]), typeof(NpgsqlRange[]), "{0}", "GetValue") + }, + + { + (int)(NpgsqlDbType.Hstore | NpgsqlDbType.Array), + ("(Dictionary[])", + "JsonConvert.DeserializeObject[]>({0})", + "JsonConvert.SerializeObject({0})", "Dictionary[]", + typeof(Dictionary[]), typeof(Dictionary[]), "{0}", + "GetValue") + }, + { + (int)(NpgsqlDbType.Geometry | NpgsqlDbType.Array), + ("(PostgisGeometry[])", "JsonConvert.DeserializeObject({0})", + "JsonConvert.SerializeObject({0})", "PostgisGeometry[]", typeof(PostgisGeometry[]), + typeof(PostgisGeometry[]), "{0}", "GetValue") + }, + }; + + 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() + { + throw new NotImplementedException(); + } + + public bool ExistsTable(string name, bool ignoreCase) + { + if (string.IsNullOrEmpty(name)) return false; + var tbnameArray = _commonUtils.SplitTableName(name); + var tbname = string.Empty; + if (tbnameArray?.Length == 1) + tbname = tbnameArray.FirstOrDefault(); + var resList = _orm.Ado.Query(CommandType.Text, @"SHOW TABLES"); + var res = false; + if (ignoreCase) + res = resList.Any(s => s.ToLower().Equals(tbname.ToLower())); + else + res = resList.Any(s => s.Equals(tbname)); + return res; + } + + public DbTableInfo GetTableByName(string name, bool ignoreCase = true) => + GetTables(null, name, ignoreCase)?.FirstOrDefault(); + + public List GetTablesByDatabase(params string[] database) => GetTables(database, null, false); + + public List GetTables(string[] database, string tablename, bool ignoreCase) + { + var resList = _orm.Ado.Query(CommandType.Text, @"SHOW TABLES"); + + var tables = new List(); + + resList.ForEach(s => + { + var tableColumns = _orm.Ado.ExecuteDataTable($"SHOW COLUMNS FROM '{s}'"); + List dbColumnInfos = new List(); + var dbTableInfo = new DbTableInfo() + { + Name = s, + Columns = new List() + }; + foreach (DataRow tableColumnsRow in tableColumns.Rows) + { + dbColumnInfos.Add(new DbColumnInfo() + { + Name = tableColumnsRow["column"].ToString(), + DbTypeText = tableColumnsRow["type"].ToString(), + Table = dbTableInfo, + }); + } + + dbTableInfo.Columns = dbColumnInfos; + tables.Add(dbTableInfo); + }); + return tables; + } + + public List GetEnumsByDatabase(params string[] database) + { + throw new NotImplementedException(); + } + + public static NativeTuple ParsePgVersion(string versionString, int v1, int v2) + { + int[] version = new int[] { 0, 0 }; + var vmatch = Regex.Match(versionString, @"(\d+)\.(\d+)"); + if (vmatch.Success) + { + version[0] = int.Parse(vmatch.Groups[1].Value); + version[1] = int.Parse(vmatch.Groups[2].Value); + } + else + { + vmatch = Regex.Match(versionString, @"(\d+)"); + version[0] = int.Parse(vmatch.Groups[1].Value); + } + + if (version[0] > v1) + return NativeTuple.Create(true, version[0], version[1]); + if (version[0] == v1 && version[1] >= v2) + return NativeTuple.Create(true, version[0], version[1]); + return NativeTuple.Create(false, version[0], version[1]); + } + } +} \ No newline at end of file diff --git a/Providers/FreeSql.Provider.QuestDb/QuestDbExpression.cs b/Providers/FreeSql.Provider.QuestDb/QuestDbExpression.cs new file mode 100644 index 00000000..b71f1135 --- /dev/null +++ b/Providers/FreeSql.Provider.QuestDb/QuestDbExpression.cs @@ -0,0 +1,831 @@ +using FreeSql.Internal; +using Newtonsoft.Json.Linq; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Text.RegularExpressions; + +namespace FreeSql.QuestDb +{ + class QuestDbExpression : CommonExpression + { + public QuestDbExpression(CommonUtils common) : base(common) + { + } + + public override string ExpressionLambdaToSqlOther(Expression exp, ExpTSC tsc) + { + Func getExp = exparg => ExpressionLambdaToSql(exparg, tsc); + switch (exp.NodeType) + { + case ExpressionType.ArrayLength: + var arrOper = (exp as UnaryExpression)?.Operand; + var arrOperExp = getExp(arrOper); + if (arrOperExp.StartsWith("(") || arrOperExp.EndsWith(")")) + return $"array_length(array[{arrOperExp.TrimStart('(').TrimEnd(')')}],1)"; + if (arrOper.Type == typeof(byte[])) return $"octet_length({getExp(arrOper)})"; + return $"case when {arrOperExp} is null then 0 else array_length({arrOperExp},1) end"; + 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 ExpressionConstDateTime(operandExp) ?? $"({getExp(operandExp)})::timestamp"; + case "System.Decimal": return $"({getExp(operandExp)})::numeric"; + case "System.Double": return $"({getExp(operandExp)})::float8"; + case "System.Int16": return $"({getExp(operandExp)})::int2"; + case "System.Int32": return $"({getExp(operandExp)})::int4"; + case "System.Int64": return $"({getExp(operandExp)})::int8"; + case "System.SByte": return $"({getExp(operandExp)})::int2"; + case "System.Single": return $"({getExp(operandExp)})::float4"; + case "System.String": return $"({getExp(operandExp)})::text"; + case "System.UInt16": return $"({getExp(operandExp)})::int2"; + case "System.UInt32": return $"({getExp(operandExp)})::int4"; + case "System.UInt64": return $"({getExp(operandExp)})::int8"; + case "System.Guid": return $"({getExp(operandExp)})::uuid"; + } + } + + break; + 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 ExpressionConstDateTime(callExp.Arguments[0]) ?? + $"({getExp(callExp.Arguments[0])})::timestamp"; + case "System.Decimal": return $"({getExp(callExp.Arguments[0])})::numeric"; + case "System.Double": return $"({getExp(callExp.Arguments[0])})::float8"; + case "System.Int16": return $"({getExp(callExp.Arguments[0])})::int2"; + case "System.Int32": return $"({getExp(callExp.Arguments[0])})::int4"; + case "System.Int64": return $"({getExp(callExp.Arguments[0])})::int8"; + case "System.SByte": return $"({getExp(callExp.Arguments[0])})::int2"; + case "System.Single": return $"({getExp(callExp.Arguments[0])})::float4"; + case "System.UInt16": return $"({getExp(callExp.Arguments[0])})::int2"; + case "System.UInt32": return $"({getExp(callExp.Arguments[0])})::int4"; + case "System.UInt64": return $"({getExp(callExp.Arguments[0])})::int8"; + case "System.Guid": return $"({getExp(callExp.Arguments[0])})::uuid"; + } + + 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)})::text" : 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 == typeof(string)) + { + switch (callExp.Method.Name) + { + case "First": + case "FirstOrDefault": + return $"substr({getExp(callExp.Arguments[0])}, 1, 1)"; + } + } + } + + if (objType == null) objType = callExp.Method.DeclaringType; + if (objType != null || objType.IsArrayOrList()) + { + string left = null; + switch (objType.FullName) + { + case "Newtonsoft.Json.Linq.JToken": + case "Newtonsoft.Json.Linq.JObject": + case "Newtonsoft.Json.Linq.JArray": + left = objExp == null ? null : getExp(objExp); + switch (callExp.Method.Name) + { + case "get_Item": return $"{left}->{getExp(callExp.Arguments[argIndex])}"; + case "Any": return $"(jsonb_array_length(coalesce({left},'[]')) > 0)"; + case "Contains": + var json = getExp(callExp.Arguments[argIndex]); + if (objType == typeof(JArray)) + return $"(coalesce({left},'[]') ? ({json})::text)"; + if (json.StartsWith("'") && json.EndsWith("'")) + return + $"(coalesce({left},'{{}}') @> {_common.FormatSql("{0}", JToken.Parse(json.Trim('\'')))})"; + return $"(coalesce({left},'{{}}') @> ({json})::jsonb)"; + case "ContainsKey": + return $"(coalesce({left},'{{}}') ? {getExp(callExp.Arguments[argIndex])})"; + case "Concat": + var right2 = getExp(callExp.Arguments[argIndex]); + return $"(coalesce({left},'{{}}') || {right2})"; + case "LongCount": + case "Count": return $"jsonb_array_length(coalesce({left},'[]'))"; + case "Parse": + var json2 = getExp(callExp.Arguments[argIndex]); + if (json2.StartsWith("'") && json2.EndsWith("'")) + return _common.FormatSql("{0}", JToken.Parse(json2.Trim('\''))); + return $"({json2})::jsonb"; + } + + break; + } + + if (objType == typeof(Dictionary)) + { + left = objExp == null ? null : getExp(objExp); + switch (callExp.Method.Name) + { + case "get_Item": return $"{left}->{getExp(callExp.Arguments[argIndex])}"; + case "Contains": + var right = getExp(callExp.Arguments[argIndex]); + return $"({left} @> ({right}))"; + case "ContainsKey": return $"({left} ? {getExp(callExp.Arguments[argIndex])})"; + case "Concat": return $"({left} || {getExp(callExp.Arguments[argIndex])})"; + case "GetLength": + case "GetLongLength": + case "Count": + return $"case when {left} is null then 0 else array_length(akeys({left}),1) end"; + case "Keys": return $"akeys({left})"; + case "Values": return $"avals({left})"; + } + } + + switch (callExp.Method.Name) + { + case "Any": + left = objExp == null ? null : getExp(objExp); + if (left.StartsWith("(") || left.EndsWith(")")) + left = $"array[{left.TrimStart('(').TrimEnd(')')}]"; + return $"(case when {left} is null then 0 else array_length({left},1) end > 0)"; + case "Contains": + tsc.SetMapColumnTmp(null); + var args1 = getExp(callExp.Arguments[argIndex]); + var oldMapType = tsc.SetMapTypeReturnOld(tsc.mapTypeTmp); + var oldDbParams = objExp?.NodeType == ExpressionType.MemberAccess + ? tsc.SetDbParamsReturnOld(null) + : null; //#900 UseGenerateCommandParameterWithLambda(true) 子查询 bug、以及 #1173 参数化 bug + tsc.isNotSetMapColumnTmp = true; + left = objExp == null ? null : getExp(objExp); + tsc.isNotSetMapColumnTmp = false; + tsc.SetMapColumnTmp(null).SetMapTypeReturnOld(oldMapType); + if (oldDbParams != null) tsc.SetDbParamsReturnOld(oldDbParams); + //判断 in 或 array @> array + if (left.StartsWith("array[") || left.EndsWith("]")) + return $"({args1}) in ({left.Substring(6, left.Length - 7)})"; + if (left.StartsWith("(") || + left.EndsWith(")")) //在各大 Provider AdoProvider 中已约定,500元素分割, 3空格\r\n4空格 + return + $"(({args1}) in {left.Replace(", \r\n \r\n", $") \r\n OR ({args1}) in (")})"; + if (args1.StartsWith("(") || args1.EndsWith(")")) + args1 = $"array[{args1.TrimStart('(').TrimEnd(')')}]"; + args1 = $"array[{args1}]"; + if (objExp != null) + { + var dbinfo = _common._orm.CodeFirst.GetDbInfo(objExp.Type); + if (dbinfo != null) args1 = $"{args1}::{dbinfo.dbtype}"; + } + + return $"({left} @> {args1})"; + case "Concat": + left = objExp == null ? null : getExp(objExp); + if (left.StartsWith("(") || left.EndsWith(")")) + left = $"array[{left.TrimStart('(').TrimEnd(')')}]"; + var right2 = getExp(callExp.Arguments[argIndex]); + if (right2.StartsWith("(") || right2.EndsWith(")")) + right2 = $"array[{right2.TrimStart('(').TrimEnd(')')}]"; + return $"({left} || {right2})"; + case "GetLength": + case "GetLongLength": + case "Length": + case "Count": + left = objExp == null ? null : getExp(objExp); + if (left.StartsWith("(") || left.EndsWith(")")) + left = $"array[{left.TrimStart('(').TrimEnd(')')}]"; + return $"case when {left} is null then 0 else array_length({left},1) end"; + } + } + + break; + case ExpressionType.MemberAccess: + var memExp = exp as MemberExpression; + var memParentExp = memExp.Expression?.Type; + if (memParentExp?.FullName == "System.Byte[]") return null; + if (memParentExp != null) + { + if (memParentExp.IsArray == true) + { + var left = getExp(memExp.Expression); + if (left.StartsWith("(") || left.EndsWith(")")) + left = $"array[{left.TrimStart('(').TrimEnd(')')}]"; + switch (memExp.Member.Name) + { + case "Length": + case "Count": return $"case when {left} is null then 0 else array_length({left},1) end"; + } + } + + switch (memParentExp.FullName) + { + case "Newtonsoft.Json.Linq.JToken": + case "Newtonsoft.Json.Linq.JObject": + case "Newtonsoft.Json.Linq.JArray": + var left = getExp(memExp.Expression); + switch (memExp.Member.Name) + { + case "Count": return $"jsonb_array_length(coalesce({left},'[]'))"; + } + + break; + } + + if (memParentExp == typeof(Dictionary)) + { + var left = getExp(memExp.Expression); + switch (memExp.Member.Name) + { + case "Count": + return $"case when {left} is null then 0 else array_length(akeys({left}),1) end"; + case "Keys": return $"akeys({left})"; + case "Values": return $"avals({left})"; + } + } + } + + break; + case ExpressionType.NewArrayInit: + var arrExp = exp as NewArrayExpression; + var arrSb = new StringBuilder(); + arrSb.Append("array["); + 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); + case "Format": + if (exp.Arguments[0].NodeType != ExpressionType.Constant) + throw new Exception( + CoreStrings.Not_Implemented_Expression_ParameterUseConstant(exp, exp.Arguments[0])); + var expArgsHack = + exp.Arguments.Count == 2 && exp.Arguments[1].NodeType == ExpressionType.NewArrayInit + ? (exp.Arguments[1] as NewArrayExpression).Expressions + : exp.Arguments.Where((a, z) => z > 0); + //3个 {} 时,Arguments 解析出来是分开的 + //4个 {} 时,Arguments[1] 只能解析这个出来,然后里面是 NewArray [] + var expArgs = expArgsHack.Select(a => + { + var atype = (a as UnaryExpression)?.Operand.Type.NullableTypeOrThis() ?? + a.Type.NullableTypeOrThis(); + if (atype == typeof(string)) + return $"'||{_common.IsNull(ExpressionLambdaToSql(a, tsc), "''")}||'"; + return $"'||{_common.IsNull($"({ExpressionLambdaToSql(a, tsc)})::text", "''")}||'"; + }).ToArray(); + return string.Format(ExpressionLambdaToSql(exp.Arguments[0], tsc), expArgs); + case "Join": + if (exp.IsStringJoin(out var tolistObjectExp, out var toListMethod, out var toListArgs1)) + { + var newToListArgs0 = Expression.Call(tolistObjectExp, toListMethod, + Expression.Lambda( + Expression.Call( + typeof(SqlExtExtensions).GetMethod("StringJoinPgsqlGroupConcat"), + Expression.Convert(toListArgs1.Body, typeof(object)), + Expression.Convert(exp.Arguments[0], typeof(object))), + toListArgs1.Parameters)); + var newToListSql = getExp(newToListArgs0); + return newToListSql; + } + + break; + } + } + 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"; + if (args0Value.Contains("%")) + { + if (exp.Method.Name == "StartsWith") return $"strpos({args0Value}, {left}) = 1"; + if (exp.Method.Name == "EndsWith") + return $"strpos({args0Value}, {left}) = char_length({args0Value})"; + return $"strpos({args0Value}, {left}) > 0"; + } + + var likeOpt = "LIKE"; + if (exp.Arguments.Count > 1) + { + if (exp.Arguments[1].Type == typeof(bool) || + exp.Arguments[1].Type == typeof(StringComparison)) likeOpt = "ILIKE"; + } + + if (exp.Method.Name == "StartsWith") + return + $"({left}) {likeOpt} {(args0Value.EndsWith("'") ? args0Value.Insert(args0Value.Length - 1, "%") : $"(({args0Value})::text || '%')")}"; + if (exp.Method.Name == "EndsWith") + return + $"({left}) {likeOpt} {(args0Value.StartsWith("'") ? args0Value.Insert(1, "%") : $"('%' || ({args0Value})::text)")}"; + if (args0Value.StartsWith("'") && args0Value.EndsWith("'")) + return $"({left}) {likeOpt} {args0Value.Insert(1, "%").Insert(args0Value.Length, "%")}"; + return $"({left}) {likeOpt} ('%' || ({args0Value})::text || '%')"; + 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])})::text)"; + } + } + + 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 ExpressionConstDateTime(exp.Arguments[0]) ?? $"({getExp(exp.Arguments[0])})::timestamp"; + case "ParseExact": + case "TryParse": + case "TryParseExact": + return ExpressionConstDateTime(exp.Arguments[0]) ?? $"({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})/1000)||' milliseconds')::interval)"; + case "AddDays": return $"(({left})::timestamp+(({args1})||' day')::interval)"; + case "AddHours": return $"(({left})::timestamp+(({args1})||' hour')::interval)"; + case "AddMilliseconds": return $"(({left})::timestamp+(({args1})||' milliseconds')::interval)"; + case "AddMinutes": return $"(({left})::timestamp+(({args1})||' minute')::interval)"; + case "AddMonths": return $"(({left})::timestamp+(({args1})||' month')::interval)"; + case "AddSeconds": return $"(({left})::timestamp+(({args1})||' second')::interval)"; + case "AddTicks": return $"(({left})::timestamp+(({args1})/10||' microseconds')::interval)"; + case "AddYears": return $"(({left})::timestamp+(({args1})||' year')::interval)"; + case "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})/1000)||' milliseconds')::interval)"; + } + + break; + case "Equals": return $"({left} = ({args1})::timestamp)"; + case "CompareTo": return $"extract(epoch from ({left})::timestamp-({args1})::timestamp)"; + case "ToString": + if (left.EndsWith("::timestamp") == false) left = $"({left})::timestamp"; + if (exp.Arguments.Count == 0) return $"to_char({left},'YYYY-MM-DD HH24:MI:SS.US')"; + switch (args1) + { + case "'yyyy-MM-dd HH:mm:ss'": return $"to_char({left},'YYYY-MM-DD HH24:MI:SS')"; + case "'yyyy-MM-dd HH:mm'": return $"to_char({left},'YYYY-MM-DD HH24:MI')"; + case "'yyyy-MM-dd HH'": return $"to_char({left},'YYYY-MM-DD HH24')"; + case "'yyyy-MM-dd'": return $"to_char({left},'YYYY-MM-DD')"; + case "'yyyy-MM'": return $"to_char({left},'YYYY-MM')"; + case "'yyyyMMddHHmmss'": return $"to_char({left},'YYYYMMDDHH24MISS')"; + case "'yyyyMMddHHmm'": return $"to_char({left},'YYYYMMDDHH24MI')"; + case "'yyyyMMddHH'": return $"to_char({left},'YYYYMMDDHH24')"; + case "'yyyyMMdd'": return $"to_char({left},'YYYYMMDD')"; + case "'yyyyMM'": return $"to_char({left},'YYYYMM')"; + case "'yyyy'": return $"to_char({left},'YYYY')"; + case "'HH:mm:ss'": return $"to_char({left},'HH24:MI:SS')"; + } + + args1 = Regex.Replace(args1, "(yyyy|yy|MM|dd|HH|hh|mm|ss|tt)", m => + { + switch (m.Groups[1].Value) + { + case "yyyy": return $"YYYY"; + case "yy": return $"YY"; + case "MM": return $"%_a1"; + case "dd": return $"%_a2"; + case "HH": return $"%_a3"; + case "hh": return $"%_a4"; + case "mm": return $"%_a5"; + case "ss": return $"SS"; + case "tt": return $"%_a6"; + } + + return m.Groups[0].Value; + }); + var argsFinds = new[] { "YYYY", "YY", "%_a1", "%_a2", "%_a3", "%_a4", "%_a5", "SS", "%_a6" }; + var argsSpts = Regex.Split(args1, "(M|d|H|h|m|s|t)"); + for (var a = 0; a < argsSpts.Length; a++) + { + switch (argsSpts[a]) + { + case "M": + argsSpts[a] = $"ltrim(to_char({left},'MM'),'0')"; + break; + case "d": + argsSpts[a] = + $"case when substr(to_char({left},'DD'),1,1) = '0' then substr(to_char({left},'DD'),2,1) else to_char({left},'DD') end"; + break; + case "H": + argsSpts[a] = + $"case when substr(to_char({left},'HH24'),1,1) = '0' then substr(to_char({left},'HH24'),2,1) else to_char({left},'HH24') end"; + break; + case "h": + argsSpts[a] = + $"case when substr(to_char({left},'HH12'),1,1) = '0' then substr(to_char({left},'HH12'),2,1) else to_char({left},'HH12') end"; + break; + case "m": + argsSpts[a] = + $"case when substr(to_char({left},'MI'),1,1) = '0' then substr(to_char({left},'MI'),2,1) else to_char({left},'MI') end"; + break; + case "s": + argsSpts[a] = + $"case when substr(to_char({left},'SS'),1,1) = '0' then substr(to_char({left},'SS'),2,1) else to_char({left},'SS') end"; + break; + case "t": + argsSpts[a] = $"rtrim(to_char({left},'AM'),'M')"; + break; + default: + var argsSptsA = argsSpts[a]; + if (argsSptsA.StartsWith("'")) argsSptsA = argsSptsA.Substring(1); + if (argsSptsA.EndsWith("'")) argsSptsA = argsSptsA.Remove(argsSptsA.Length - 1); + argsSpts[a] = argsFinds.Any(m => argsSptsA.Contains(m)) + ? $"to_char({left},'{argsSptsA}')" + : $"'{argsSptsA}'"; + break; + } + } + + if (argsSpts.Length > 0) args1 = $"({string.Join(" || ", argsSpts.Where(a => a != "''"))})"; + return args1.Replace("%_a1", "MM").Replace("%_a2", "DD").Replace("%_a3", "HH24") + .Replace("%_a4", "HH12").Replace("%_a5", "MI").Replace("%_a6", "AM"); + } + } + + return 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 ExpressionConstDateTime(exp.Arguments[0]) ?? $"({getExp(exp.Arguments[0])})::timestamp"; + case "ToDecimal": return $"({getExp(exp.Arguments[0])})::numeric"; + case "ToDouble": return $"({getExp(exp.Arguments[0])})::float8"; + case "ToInt16": return $"({getExp(exp.Arguments[0])})::int2"; + case "ToInt32": return $"({getExp(exp.Arguments[0])})::int4"; + case "ToInt64": return $"({getExp(exp.Arguments[0])})::int8"; + case "ToSByte": return $"({getExp(exp.Arguments[0])})::int2"; + case "ToSingle": return $"({getExp(exp.Arguments[0])})::float4"; + case "ToString": return $"({getExp(exp.Arguments[0])})::text"; + case "ToUInt16": return $"({getExp(exp.Arguments[0])})::int2"; + case "ToUInt32": return $"({getExp(exp.Arguments[0])})::int4"; + case "ToUInt64": return $"({getExp(exp.Arguments[0])})::int8"; + } + } + + return null; + } + } + + class QuestDbExpressionVisitor : ExpressionVisitor + { + private List list = new List(); + + protected override Expression VisitMember(MemberExpression node) + { + list.Add(node.Member.Name); + return node; + } + + internal string Fields() + { + var fileds = string.Join(",", list); + list.Clear(); + return fileds; + } + } +} \ No newline at end of file diff --git a/Providers/FreeSql.Provider.QuestDb/QuestDbGlobalExtensions.cs b/Providers/FreeSql.Provider.QuestDb/QuestDbGlobalExtensions.cs new file mode 100644 index 00000000..8330c4f8 --- /dev/null +++ b/Providers/FreeSql.Provider.QuestDb/QuestDbGlobalExtensions.cs @@ -0,0 +1,253 @@ +using FreeSql; +using FreeSql.Internal.CommonProvider; +using FreeSql.Internal.Model; +using FreeSql.QuestDb; +using FreeSql.QuestDb.Curd; +using Npgsql; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +public static partial class QuestDbGlobalExtensions +{ + + /// + /// 特殊处理类似 string.Format 的使用方法,防止注入,以及 IS NULL 转换 + /// + /// + /// + /// + public static string FormatPostgreSQL(this string that, params object[] args) => _postgresqlAdo.Addslashes(that, args); + static QuestDbAdo _postgresqlAdo = new QuestDbAdo(); + + /// + /// PostgreSQL9.5+ 特有的功能,On Conflict Do Update + /// 注意:此功能会开启插入【自增列】 + /// + /// + /// + /// 默认是以主键作为重复判断,也可以指定其他列:a => a.Name | a => new{a.Name,a.Time} | a => new[]{"name","time"} + /// + public static OnConflictDoUpdate OnConflictDoUpdate(this IInsert that, Expression> columns = null) where T1 : class => new OnConflictDoUpdate(that.InsertIdentity(), columns); + + #region ExecutePgCopy + /// + /// 批量更新(更新字段数量超过 2000 时收益大) + /// 实现原理:使用 PgCopy 插入临时表,再使用 UPDATE INNER JOIN 联表更新 + /// + /// + /// + /// + public static int ExecutePgCopy(this IUpdate that) where T : class + { + var update = that as UpdateProvider; + if (update._source.Any() != true || update._tempPrimarys.Any() == false) return 0; + var state = ExecutePgCopyState(update); + return UpdateProvider.ExecuteBulkUpdate(update, state, insert => insert.ExecutePgCopy()); + } + static NativeTuple ExecutePgCopyState(UpdateProvider update) where T : class + { + if (update._source.Any() != true) return null; + var _table = update._table; + var _commonUtils = update._commonUtils; + var updateTableName = update._tableRule?.Invoke(_table.DbName) ?? _table.DbName; + var tempTableName = $"Temp_{Guid.NewGuid().ToString("N")}"; + if (update._orm.CodeFirst.IsSyncStructureToLower) tempTableName = tempTableName.ToLower(); + if (update._orm.CodeFirst.IsSyncStructureToUpper) tempTableName = tempTableName.ToUpper(); + if (update._connection == null && update._orm.Ado.TransactionCurrentThread != null) + update.WithTransaction(update._orm.Ado.TransactionCurrentThread); + var sb = new StringBuilder().Append("CREATE TEMP TABLE ").Append(_commonUtils.QuoteSqlName(tempTableName)).Append(" ( "); + var setColumns = new List(); + var pkColumns = new List(); + foreach (var col in _table.Columns.Values) + { + if (update._tempPrimarys.Any(a => a.CsName == col.CsName)) pkColumns.Add(col.Attribute.Name); + else if (col.Attribute.IsIdentity == false && col.Attribute.IsVersion == false && update._ignore.ContainsKey(col.Attribute.Name) == false) setColumns.Add(col.Attribute.Name); + else continue; + sb.Append(" \r\n ").Append(_commonUtils.QuoteSqlName(col.Attribute.Name)).Append(" ").Append(col.Attribute.DbType.Replace("NOT NULL", "")); + sb.Append(","); + } + var sql1 = sb.Remove(sb.Length - 1, 1).Append("\r\n) WITH (OIDS=FALSE);").ToString(); + + sb.Clear().Append("UPDATE ").Append(_commonUtils.QuoteSqlName(updateTableName)).Append(" a ") + .Append("\r\nSET \r\n ").Append(string.Join(", \r\n ", setColumns.Select(col => $"{_commonUtils.QuoteSqlName(col)} = b.{_commonUtils.QuoteSqlName(col)}"))) + .Append("\r\nFROM ").Append(_commonUtils.QuoteSqlName(tempTableName)).Append(" b ") + .Append("\r\nWHERE ").Append(string.Join(" AND ", pkColumns.Select(col => $"a.{_commonUtils.QuoteSqlName(col)} = b.{_commonUtils.QuoteSqlName(col)}"))); + var sql2 = sb.ToString(); + sb.Clear(); + var sql3 = $"DROP TABLE {_commonUtils.QuoteSqlName(tempTableName)}"; + return NativeTuple.Create(sql1, sql2, sql3, tempTableName, pkColumns.Concat(setColumns).ToArray()); + } + + /// + /// PostgreSQL COPY 批量导入功能,封装了 NpgsqlConnection.BeginBinaryImport 方法 + /// 使用 IgnoreColumns/InsertColumns 设置忽略/指定导入的列 + /// 使用 WithConnection/WithTransaction 传入连接/事务对象 + /// 提示:若本方法不能满足,请使用 IInsert<T>.ToDataTable 方法得到 DataTable 对象后,自行处理。 + /// COPY 与 insert into t values(..),(..),(..) 性能测试参考: + /// 插入180000行,52列:10,090ms 与 46,756ms,10列:4,081ms 与 9,786ms + /// 插入10000行,52列:583ms 与 3,294ms,10列:167ms 与 568ms + /// 插入5000行,52列:337ms 与 2,269ms,10列:93ms 与 366ms + /// 插入2000行,52列:136ms 与 1,019ms,10列:39ms 与 157ms + /// 插入1000行,52列:88ms 与 374ms,10列:21ms 与 102ms + /// 插入500行,52列:61ms 与 209ms,10列:12ms 与 34ms + /// 插入100行,52列:30ms 与 51ms,10列:4ms 与 9ms + /// 插入50行,52列:25ms 与 37ms,10列:2ms 与 6ms + /// + /// + /// + public static void ExecutePgCopy(this IInsert that) where T : class + { + var insert = that as QuestDbInsert; + if (insert == null) throw new Exception(CoreStrings.S_Features_Unique("ExecutePgCopy", "PostgreSQL")); + + var dt = that.ToDataTable(); + if (dt.Rows.Count == 0) return; + + Action binaryImport = conn => + { + var copyFromCommand = new StringBuilder().Append("COPY ").Append(insert.InternalCommonUtils.QuoteSqlName(dt.TableName)).Append("("); + var colIndex = 0; + foreach (DataColumn col in dt.Columns) + { + if (colIndex++ > 0) copyFromCommand.Append(", "); + copyFromCommand.Append(insert.InternalCommonUtils.QuoteSqlName(col.ColumnName)); + } + copyFromCommand.Append(") FROM STDIN BINARY"); + using (var writer = conn.BeginBinaryImport(copyFromCommand.ToString())) + { + foreach (DataRow item in dt.Rows) + writer.WriteRow(item.ItemArray); + writer.Complete(); + } + copyFromCommand.Clear(); + }; + + try + { + if (insert.InternalConnection == null && insert.InternalTransaction == null) + { + using (var conn = insert.InternalOrm.Ado.MasterPool.Get()) + { + binaryImport(conn.Value as NpgsqlConnection); + } + } + else if (insert.InternalTransaction != null) + { + binaryImport(insert.InternalTransaction.Connection as NpgsqlConnection); + } + else if (insert.InternalConnection != null) + { + var conn = insert.InternalConnection as NpgsqlConnection; + var isNotOpen = false; + if (conn.State != System.Data.ConnectionState.Open) + { + isNotOpen = true; + conn.Open(); + } + try + { + binaryImport(conn); + } + finally + { + if (isNotOpen) + conn.Close(); + } + } + else + { + throw new NotImplementedException($"ExecutePgCopy {CoreStrings.S_Not_Implemented_FeedBack}"); + } + } + finally + { + dt.Clear(); + } + } + +#if net45 +#else + public static Task ExecutePgCopyAsync(this IUpdate that, CancellationToken cancellationToken = default) where T : class + { + var update = that as UpdateProvider; + if (update._source.Any() != true || update._tempPrimarys.Any() == false) return Task.FromResult(0); + var state = ExecutePgCopyState(update); + return UpdateProvider.ExecuteBulkUpdateAsync(update, state, insert => insert.ExecutePgCopyAsync(cancellationToken)); + } + async public static Task ExecutePgCopyAsync(this IInsert that, CancellationToken cancellationToken = default) where T : class + { + var insert = that as QuestDbInsert; + if (insert == null) throw new Exception(CoreStrings.S_Features_Unique("ExecutePgCopyAsync", "PostgreSQL")); + + var dt = that.ToDataTable(); + if (dt.Rows.Count == 0) return; + Func binaryImportAsync = async conn => + { + var copyFromCommand = new StringBuilder().Append("COPY ").Append(insert.InternalCommonUtils.QuoteSqlName(dt.TableName)).Append("("); + var colIndex = 0; + foreach (DataColumn col in dt.Columns) + { + if (colIndex++ > 0) copyFromCommand.Append(", "); + copyFromCommand.Append(insert.InternalCommonUtils.QuoteSqlName(col.ColumnName)); + } + copyFromCommand.Append(") FROM STDIN BINARY"); + using (var writer = conn.BeginBinaryImport(copyFromCommand.ToString())) + { + foreach (DataRow item in dt.Rows) + await writer.WriteRowAsync(cancellationToken, item.ItemArray); + writer.Complete(); + } + copyFromCommand.Clear(); + }; + + try + { + if (insert.InternalConnection == null && insert.InternalTransaction == null) + { + using (var conn = await insert.InternalOrm.Ado.MasterPool.GetAsync()) + { + await binaryImportAsync(conn.Value as NpgsqlConnection); + } + } + else if (insert.InternalTransaction != null) + { + await binaryImportAsync(insert.InternalTransaction.Connection as NpgsqlConnection); + } + else if (insert.InternalConnection != null) + { + var conn = insert.InternalConnection as NpgsqlConnection; + var isNotOpen = false; + if (conn.State != System.Data.ConnectionState.Open) + { + isNotOpen = true; + await conn.OpenAsync(cancellationToken); + } + try + { + await binaryImportAsync(conn); + } + finally + { + if (isNotOpen) + await conn.CloseAsync(); + } + } + else + { + throw new NotImplementedException($"ExecutePgCopyAsync {CoreStrings.S_Not_Implemented_FeedBack}"); + } + } + finally + { + dt.Clear(); + } + } +#endif + #endregion +} diff --git a/Providers/FreeSql.Provider.QuestDb/QuestDbProvider.cs b/Providers/FreeSql.Provider.QuestDb/QuestDbProvider.cs new file mode 100644 index 00000000..e31caf4a --- /dev/null +++ b/Providers/FreeSql.Provider.QuestDb/QuestDbProvider.cs @@ -0,0 +1,185 @@ +using FreeSql.Internal; +using FreeSql.Internal.CommonProvider; +using FreeSql.PostgreSQL.Curd; +using FreeSql.QuestDb.Curd; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Npgsql.LegacyPostgis; +using NpgsqlTypes; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data.Common; +using System.Linq.Expressions; +using System.Net; +using System.Net.NetworkInformation; +using System.Numerics; +using System.Reflection; +using System.Threading; + +namespace FreeSql.QuestDb +{ + public class QuestDbProvider : BaseDbProvider, IFreeSql + { + static QuestDbProvider() + { + Utils.dicExecuteArrayRowReadClassOrTuple[typeof(BigInteger)] = true; + Utils.dicExecuteArrayRowReadClassOrTuple[typeof(BitArray)] = true; + Utils.dicExecuteArrayRowReadClassOrTuple[typeof(NpgsqlPoint)] = true; + Utils.dicExecuteArrayRowReadClassOrTuple[typeof(NpgsqlLine)] = true; + Utils.dicExecuteArrayRowReadClassOrTuple[typeof(NpgsqlLSeg)] = true; + Utils.dicExecuteArrayRowReadClassOrTuple[typeof(NpgsqlBox)] = true; + Utils.dicExecuteArrayRowReadClassOrTuple[typeof(NpgsqlPath)] = true; + Utils.dicExecuteArrayRowReadClassOrTuple[typeof(NpgsqlPolygon)] = true; + Utils.dicExecuteArrayRowReadClassOrTuple[typeof(NpgsqlCircle)] = true; + Utils.dicExecuteArrayRowReadClassOrTuple[typeof((IPAddress Address, int Subnet))] = true; + Utils.dicExecuteArrayRowReadClassOrTuple[typeof(IPAddress)] = true; + Utils.dicExecuteArrayRowReadClassOrTuple[typeof(PhysicalAddress)] = true; + Utils.dicExecuteArrayRowReadClassOrTuple[typeof(NpgsqlRange)] = true; + Utils.dicExecuteArrayRowReadClassOrTuple[typeof(NpgsqlRange)] = true; + Utils.dicExecuteArrayRowReadClassOrTuple[typeof(NpgsqlRange)] = true; + Utils.dicExecuteArrayRowReadClassOrTuple[typeof(NpgsqlRange)] = true; + + Utils.dicExecuteArrayRowReadClassOrTuple[typeof(PostgisPoint)] = true; + Utils.dicExecuteArrayRowReadClassOrTuple[typeof(PostgisLineString)] = true; + Utils.dicExecuteArrayRowReadClassOrTuple[typeof(PostgisPolygon)] = true; + Utils.dicExecuteArrayRowReadClassOrTuple[typeof(PostgisMultiPoint)] = true; + Utils.dicExecuteArrayRowReadClassOrTuple[typeof(PostgisMultiLineString)] = true; + Utils.dicExecuteArrayRowReadClassOrTuple[typeof(PostgisMultiPolygon)] = true; + Utils.dicExecuteArrayRowReadClassOrTuple[typeof(PostgisGeometry)] = true; + Utils.dicExecuteArrayRowReadClassOrTuple[typeof(PostgisGeometryCollection)] = true; + +#if nts + Utils.dicExecuteArrayRowReadClassOrTuple[typeof(NetTopologySuite.Geometries.Point)] = true; + Utils.dicExecuteArrayRowReadClassOrTuple[typeof(NetTopologySuite.Geometries.LineString)] = true; + Utils.dicExecuteArrayRowReadClassOrTuple[typeof(NetTopologySuite.Geometries.Polygon)] = true; + Utils.dicExecuteArrayRowReadClassOrTuple[typeof(NetTopologySuite.Geometries.MultiPoint)] = true; + Utils.dicExecuteArrayRowReadClassOrTuple[typeof(NetTopologySuite.Geometries.MultiLineString)] = true; + Utils.dicExecuteArrayRowReadClassOrTuple[typeof(NetTopologySuite.Geometries.MultiPolygon)] = true; + Utils.dicExecuteArrayRowReadClassOrTuple[typeof(NetTopologySuite.Geometries.Geometry)] = true; + Utils.dicExecuteArrayRowReadClassOrTuple[typeof(NetTopologySuite.Geometries.GeometryCollection)] = true; +#endif + + Utils.dicExecuteArrayRowReadClassOrTuple[typeof(Dictionary)] = true; + Utils.dicExecuteArrayRowReadClassOrTuple[typeof(JToken)] = true; + Utils.dicExecuteArrayRowReadClassOrTuple[typeof(JObject)] = true; + Utils.dicExecuteArrayRowReadClassOrTuple[typeof(JArray)] = true; + + var MethodJTokenFromObject = typeof(JToken).GetMethod("FromObject", new[] { typeof(object) }); + var MethodJObjectFromObject = typeof(JObject).GetMethod("FromObject", new[] { typeof(object) }); + var MethodJArrayFromObject = typeof(JArray).GetMethod("FromObject", new[] { typeof(object) }); + var MethodJTokenParse = typeof(JToken).GetMethod("Parse", new[] { typeof(string) }); + var MethodJObjectParse = typeof(JObject).GetMethod("Parse", new[] { typeof(string) }); + var MethodJArrayParse = typeof(JArray).GetMethod("Parse", new[] { typeof(string) }); + var MethodJsonConvertDeserializeObject = + typeof(JsonConvert).GetMethod("DeserializeObject", new[] { typeof(string), typeof(Type) }); + var MethodToString = typeof(Utils).GetMethod("ToStringConcat", BindingFlags.Public | BindingFlags.Static, + null, new[] { typeof(object) }, null); + Utils.GetDataReaderValueBlockExpressionSwitchTypeFullName.Add( + (LabelTarget returnTarget, Expression valueExp, Type type) => + { + switch (type.FullName) + { + case "Newtonsoft.Json.Linq.JToken": + return Expression.IfThenElse( + Expression.TypeIs(valueExp, typeof(string)), + Expression.Return(returnTarget, + Expression.TypeAs( + Expression.Call(MethodJTokenParse, + Expression.Convert(valueExp, typeof(string))), typeof(JToken))), + Expression.Return(returnTarget, + Expression.TypeAs(Expression.Call(MethodJTokenFromObject, valueExp), + typeof(JToken)))); + case "Newtonsoft.Json.Linq.JObject": + return Expression.IfThenElse( + Expression.TypeIs(valueExp, typeof(string)), + Expression.Return(returnTarget, + Expression.TypeAs( + Expression.Call(MethodJObjectParse, + Expression.Convert(valueExp, typeof(string))), typeof(JObject))), + Expression.Return(returnTarget, + Expression.TypeAs(Expression.Call(MethodJObjectFromObject, valueExp), + typeof(JObject)))); + case "Newtonsoft.Json.Linq.JArray": + return Expression.IfThenElse( + Expression.TypeIs(valueExp, typeof(string)), + Expression.Return(returnTarget, + Expression.TypeAs( + Expression.Call(MethodJArrayParse, + Expression.Convert(valueExp, typeof(string))), typeof(JArray))), + Expression.Return(returnTarget, + Expression.TypeAs(Expression.Call(MethodJArrayFromObject, valueExp), + typeof(JArray)))); + case "Npgsql.LegacyPostgis.PostgisGeometry": + return Expression.Return(returnTarget, valueExp); + case "NetTopologySuite.Geometries.Geometry": + return Expression.Return(returnTarget, valueExp); + } + + if (typeof(IList).IsAssignableFrom(type)) + return Expression.IfThenElse( + Expression.TypeIs(valueExp, typeof(string)), + Expression.Return(returnTarget, + Expression.TypeAs( + Expression.Call(MethodJsonConvertDeserializeObject, + Expression.Convert(valueExp, typeof(string)), + Expression.Constant(type, typeof(Type))), type)), + Expression.Return(returnTarget, + Expression.TypeAs( + Expression.Call(MethodJsonConvertDeserializeObject, + Expression.Convert(Expression.Call(MethodToString, valueExp), typeof(string)), + Expression.Constant(type, typeof(Type))), type))); + return null; + }); + + Select0Provider._dicMethodDataReaderGetValue[typeof(Guid)] = + typeof(DbDataReader).GetMethod("GetGuid", new Type[] { typeof(int) }); + } + + public override ISelect CreateSelectProvider(object dywhere) => + new QuestDbSelect(this, this.InternalCommonUtils, this.InternalCommonExpression, dywhere); + + public override IInsert CreateInsertProvider() => + new QuestDbInsert(this, this.InternalCommonUtils, this.InternalCommonExpression); + + public override IUpdate CreateUpdateProvider(object dywhere) => + new QuestDbUpdate(this, this.InternalCommonUtils, this.InternalCommonExpression, dywhere); + + public override IDelete CreateDeleteProvider(object dywhere) => + new QuestDbDelete(this, this.InternalCommonUtils, this.InternalCommonExpression, dywhere); + + public override IInsertOrUpdate CreateInsertOrUpdateProvider() => + new QuestDbInsertOrUpdate(this, this.InternalCommonUtils, this.InternalCommonExpression); + + public QuestDbProvider(string masterConnectionString, string[] slaveConnectionString, + Func connectionFactory = null) + { + this.InternalCommonUtils = new QuestDbUtils(this); + this.InternalCommonExpression = new QuestDbExpression(this.InternalCommonUtils); + + this.Ado = new QuestDbAdo(this.InternalCommonUtils, masterConnectionString, slaveConnectionString, + connectionFactory); + this.Aop = new AopProvider(); + + this.DbFirst = new QuestDbDbFirst(this, this.InternalCommonUtils, this.InternalCommonExpression); + this.CodeFirst = new QuestDbCodeFirst(this, this.InternalCommonUtils, this.InternalCommonExpression); + + //this.Aop.AuditDataReader += (_, e) => + //{ + // var dbtype = e.DataReader.GetDataTypeName(e.Index); + // var m = Regex.Match(dbtype, @"numeric\((\d+)\)", RegexOptions.IgnoreCase); + // if (m.Success && int.Parse(m.Groups[1].Value) > 19) + // e.Value = e.DataReader.GetFieldValue(e.Index); + //}; + } + + ~QuestDbProvider() => this.Dispose(); + int _disposeCounter; + + public override void Dispose() + { + if (Interlocked.Increment(ref _disposeCounter) != 1) return; + (this.Ado as AdoProvider)?.Dispose(); + } + } +} \ No newline at end of file diff --git a/Providers/FreeSql.Provider.QuestDb/QuestDbUtils.cs b/Providers/FreeSql.Provider.QuestDb/QuestDbUtils.cs new file mode 100644 index 00000000..f898bf64 --- /dev/null +++ b/Providers/FreeSql.Provider.QuestDb/QuestDbUtils.cs @@ -0,0 +1,522 @@ +using FreeSql.Internal; +using FreeSql.Internal.Model; +using Newtonsoft.Json.Linq; +using Npgsql; +using Npgsql.LegacyPostgis; +using NpgsqlTypes; +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Globalization; +using System.Linq; +using System.Linq.Expressions; +using System.Net; +using System.Numerics; +using System.Text; +using System.Threading; +using FreeSql.QuestDb; + +namespace FreeSql.QuestDb +{ + class QuestDbUtils : CommonUtils + { + public QuestDbUtils(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(JToken).FullName, a => string.Concat(a) }, + { typeof(JToken[]).FullName, a => getParamterArrayValue(typeof(string), a, null) }, + { typeof(JObject).FullName, a => string.Concat(a) }, + { typeof(JObject[]).FullName, a => getParamterArrayValue(typeof(string), a, null) }, + { typeof(JArray).FullName, a => string.Concat(a) }, + { typeof(JArray[]).FullName, a => getParamterArrayValue(typeof(string), a, null) }, + { 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) }, + { typeof(char).FullName, a => string.Concat(a).Replace('\0', ' ').ToCharArray().FirstOrDefault() }, + { + typeof(BigInteger).FullName, + a => BigInteger.Parse(string.Concat(a), System.Globalization.NumberStyles.Any) + }, + { typeof(BigInteger[]).FullName, a => getParamterArrayValue(typeof(BigInteger), a, 0) }, + { typeof(BigInteger?[]).FullName, a => getParamterArrayValue(typeof(BigInteger?), a, null) }, + + { + typeof(NpgsqlPath).FullName, a => + { + var path = (NpgsqlPath)a; + try + { + int count = path.Count; + return path; + } + catch + { + return new NpgsqlPath(new NpgsqlPoint(0, 0)); + } + } + }, + { + typeof(NpgsqlPath[]).FullName, + a => getParamterArrayValue(typeof(NpgsqlPath), a, new NpgsqlPath(new NpgsqlPoint(0, 0))) + }, + { typeof(NpgsqlPath?[]).FullName, a => getParamterArrayValue(typeof(NpgsqlPath?), a, null) }, + + { + typeof(NpgsqlPolygon).FullName, a => + { + var polygon = (NpgsqlPolygon)a; + try + { + int count = polygon.Count; + return polygon; + } + catch + { + return new NpgsqlPolygon(new NpgsqlPoint(0, 0), new NpgsqlPoint(0, 0)); + } + } + }, + { + typeof(NpgsqlPolygon[]).FullName, + a => getParamterArrayValue(typeof(NpgsqlPolygon), a, + new NpgsqlPolygon(new NpgsqlPoint(0, 0), new NpgsqlPoint(0, 0))) + }, + { typeof(NpgsqlPolygon?[]).FullName, a => getParamterArrayValue(typeof(NpgsqlPolygon?), a, null) }, + + { + typeof((IPAddress Address, int Subnet)).FullName, a => + { + var inet = ((IPAddress Address, int Subnet))a; + if (inet.Address == null) return (IPAddress.Any, inet.Subnet); + return inet; + } + }, + { + typeof((IPAddress Address, int Subnet)[]).FullName, + a => getParamterArrayValue(typeof((IPAddress Address, int Subnet)), a, (IPAddress.Any, 0)) + }, + { + typeof((IPAddress Address, int Subnet)?[]).FullName, + a => getParamterArrayValue(typeof((IPAddress Address, int Subnet)?), a, null) + }, + }; + + static object getParamterValue(Type type, object value, int level = 0) + { + if (type.FullName == "System.Byte[]") return value; + if (type.FullName == "System.Char[]") return value; + if (type.IsArray && level == 0) + { + var elementType = type.GetElementType(); + Type enumType = null; + if (elementType.IsEnum) enumType = elementType; + else if (elementType.IsNullableType() && elementType.GenericTypeArguments.First().IsEnum) + enumType = elementType.GenericTypeArguments.First(); + if (enumType != null) + return enumType.GetCustomAttributes(typeof(FlagsAttribute), false).Any() + ? getParamterArrayValue(typeof(long), value, + elementType.IsEnum ? null : enumType.CreateInstanceGetDefaultValue()) + : getParamterArrayValue(typeof(int), value, + elementType.IsEnum ? null : enumType.CreateInstanceGetDefaultValue()); + return dicGetParamterValue.TryGetValue(type.FullName, out var trydicarr) ? trydicarr(value) : value; + } + + if (type.IsNullableType()) type = type.GenericTypeArguments.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 NpgsqlParameter { ParameterName = QuoteParamterName(parameterName), Value = value }; + + if (value != null && (value.GetType().IsEnum || + value.GetType().GenericTypeArguments.FirstOrDefault()?.IsEnum == true)) + { + ret.DataTypeName = ""; + } + else + { + var tp = _orm.CodeFirst.GetDbInfo(type)?.type; + if (tp != null) + { + var ntp = (NpgsqlDbType)tp; + switch (ntp) + { + case NpgsqlDbType.Boolean: + ret.NpgsqlDbType = NpgsqlDbType.Text; + ret.DbType = DbType.String; + ret.Value = string.Concat(value); + break; + case NpgsqlDbType.Numeric: + ret.NpgsqlDbType = NpgsqlDbType.Double; + ret.DbType = DbType.Double; + ret.Value = Convert.ToDouble(FormatSql("{0}", value)); + break; + default: + ret.NpgsqlDbType = (NpgsqlDbType)tp.Value; + break; + } + } + + if (col != null) + { + var dbtype = (NpgsqlDbType)_orm.DbFirst.GetDbType(new DatabaseModel.DbColumnInfo + { DbTypeText = col.DbTypeText }); + if (dbtype != NpgsqlDbType.Unknown) + { + ret.NpgsqlDbType = dbtype; + //if (col.DbSize != 0) ret.Size = col.DbSize; + if (col.DbPrecision != 0) + ret.Precision = col.DbPrecision; + if (col.DbScale != 0) + ret.Scale = col.DbScale; + } + } + } + + + _params?.Add(ret); + return ret; + } + + public override DbParameter[] GetDbParamtersByObject(string sql, object obj) => + Utils.GetDbParamtersByObject(sql, obj, "@", (name, type, value) => + { + if (value != null) value = getParamterValue(type, value); + var ret = new NpgsqlParameter { 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.NpgsqlDbType = (NpgsqlDbType)tp.Value; + //} + return ret; + }); + + public override string FormatSql(string sql, params object[] args) => sql?.FormatPostgreSQL(args); + + public override string QuoteSqlNameAdapter(params string[] name) + { + if (name.Length == 1) + { + var nametrim = name[0].Trim(); + if (nametrim.StartsWith("(") && nametrim.EndsWith(")")) + return nametrim; //原生SQL + if (nametrim.StartsWith("\"") && nametrim.EndsWith("\"")) + return nametrim; + return $"\"{nametrim.Replace(".", "\".\"")}\""; + } + + return $"\"{string.Join("\".\"", name)}\""; + } + + 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[] SplitTableName(string name) => GetSplitTableNames(name, '"', '"', 2); + public override string QuoteParamterName(string name) => $"@{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 QuoteWriteParamterAdapter(Type type, string paramterName) => paramterName; + protected override string QuoteReadColumnAdapter(Type type, Type mapType, string columnName) => columnName; + + static ConcurrentDictionary _dicIsAssignableFromPostgisGeometry = + new ConcurrentDictionary(); + + public override string GetNoneParamaterSqlValue(List specialParams, string specialParamFlag, + ColumnInfo col, Type type, object value) + { + if (value == null) return "NULL"; + if (type.IsNumberType()) return string.Format(CultureInfo.InvariantCulture, "{0}", value); + if (_dicIsAssignableFromPostgisGeometry.GetOrAdd(type, t2 => + { + var t2type = t2.IsArray ? t2.GetElementType() : t2; + return typeof(PostgisGeometry).IsAssignableFrom(t2type) +#if nts + || + typeof(NetTopologySuite.Geometries.Geometry).IsAssignableFrom(t2type) +#endif + ; + })) + { + var pam = AppendParamter(specialParams, $"p_{specialParams?.Count}{specialParamFlag}", null, type, + value); + return pam.ParameterName; + } + + value = getParamterValue(type, value); + var type2 = value.GetType(); + if (type2 == typeof(byte[])) return $"'\\x{CommonUtils.BytesSqlRaw(value as byte[])}'"; + if (type2 == typeof(TimeSpan) || type2 == typeof(TimeSpan?)) + { + var ts = (TimeSpan)value; + return $"'{Math.Min(24, (int)Math.Floor(ts.TotalHours))}:{ts.Minutes}:{ts.Seconds}'"; + } + else if (value is Array) + { + var valueArr = value as Array; + var eleType = type2.GetElementType(); + var len = valueArr.GetLength(0); + var sb = new StringBuilder().Append("ARRAY["); + for (var a = 0; a < len; a++) + { + var item = valueArr.GetValue(a); + if (a > 0) sb.Append(","); + sb.Append(GetNoneParamaterSqlValue(specialParams, specialParamFlag, col, eleType, item)); + } + + sb.Append("]"); + var dbinfo = _orm.CodeFirst.GetDbInfo(type); + if (dbinfo != null) sb.Append("::").Append(dbinfo.dbtype); + return sb.ToString(); + } + else if (type2 == typeof(BitArray)) + { + return $"'{(value as BitArray).To1010()}'"; + } + else if (type2 == typeof(NpgsqlLine) || type2 == typeof(NpgsqlLine?)) + { + var line = value.ToString(); + return line == "{0,0,0}" ? "'{0,-1,-1}'" : $"'{line}'"; + } + else if (type2 == typeof((IPAddress Address, int Subnet)) || + type2 == typeof((IPAddress Address, int Subnet)?)) + { + var cidr = ((IPAddress Address, int Subnet))value; + return $"'{cidr.Address}/{cidr.Subnet}'"; + } + else if (dicGetParamterValue.ContainsKey(type2.FullName)) + { + value = string.Concat(value); + } + + return FormatSql("{0}", value, 1); + } + } +} + +namespace FreeSql +{ + public enum SampleUnits + { + /// + /// 微秒 + /// + U, + + /// + /// 毫秒 + /// + T, + + /// + /// 秒 + /// + s, + + /// + /// 分钟 + /// + m, + + /// + /// 时 + /// + h, + + /// + /// 天 + /// + d, + + /// + /// 月 + /// + M + } + + public static class SampleByExtension + { + //是否使用该方法 + internal static AsyncLocal IsExistence = new AsyncLocal() + { + Value = false + }; + + internal static AsyncLocal SamoleByString = new AsyncLocal() + { + Value = string.Empty + }; + + public static void Initialize() + { + IsExistence.Value = false; + SamoleByString.Value = string.Empty; + } + + /// + /// SAMPLE BY用于时间序列数据,将大型数据集汇总为同质时间块的聚合,作为SELECT语句的一部分。对缺少数据的数据集执行查询的用户可以使用FILL关键字指定填充行为 + /// + /// + /// + /// 时长 + /// 单位 + /// + public static ISelect SampleBy(this ISelect select, double time, SampleUnits unit) + { + var _unit = Enum.GetName(typeof(SampleUnits), unit); + IsExistence.Value = true; + var samoleByTemple = $"{Environment.NewLine}SAMPLE BY {{0}}{{1}}{Environment.NewLine}"; + SamoleByString.Value = string.Format(samoleByTemple, time.ToString(), _unit); + return select; + } + } + + public static class LatestOnExtension + { + //是否使用该方法 + internal static AsyncLocal IsExistence = new AsyncLocal() + { + Value = false + }; + + internal static AsyncLocal LatestOnString = new AsyncLocal() + { + Value = string.Empty + }; + + public static void Initialize() + { + IsExistence.Value = false; + LatestOnString.Value = string.Empty; + } + + /// + /// 对于多个时间序列存储在同一个表中的场景,根据时间戳检索给定键或键组合的最新项。 + /// + /// + /// + /// + /// 时间标识 + /// 最新项的列 + /// + public static ISelect LatestOn(this ISelect select, Expression> timestamp, + Expression> partition) + { + Provider(timestamp, partition); + return select; + } + + private static void Provider(Expression> timestamp, + Expression> partition) + { + IsExistence.Value = true; + var latestOnTemple = $"{Environment.NewLine}LATEST ON {{0}} PARTITION BY {{1}} "; + var expressionVisitor = new QuestDbExpressionVisitor(); + expressionVisitor.Visit(timestamp); + var _timestamp = expressionVisitor.Fields(); + expressionVisitor.Visit(partition); + var _partition = expressionVisitor.Fields(); + LatestOnString.Value = string.Format(latestOnTemple, _timestamp, _partition); + } + + /// + /// 对于多个时间序列存储在同一个表中的场景,根据时间戳检索给定键或键组合的最新项。 + /// + /// + /// + /// + /// 时间标识 + /// 最新项的列 + /// + public static ISelect LatestOn(this ISelect select, + Expression> timestamp, + Expression> partition) where T2 : class + { + Provider(timestamp, partition); + return select; + } + + /// + /// 对于多个时间序列存储在同一个表中的场景,根据时间戳检索给定键或键组合的最新项。 + /// + /// + /// + /// + /// 时间标识 + /// 最新项的列 + /// + public static ISelect LatestOn(this ISelect select, + Expression> timestamp, + Expression> partition) where T2 : class where T3 : class + { + Provider(timestamp, partition); + return select; + } + + /// + /// 对于多个时间序列存储在同一个表中的场景,根据时间戳检索给定键或键组合的最新项。 + /// + /// + /// + /// + /// 时间标识 + /// 最新项的列 + /// + public static ISelect LatestOn(this ISelect select, + Expression> timestamp, + Expression> partition) where T2 : class where T3 : class where T4 : class + { + Provider(timestamp, partition); + return select; + } + } +} \ No newline at end of file diff --git a/Providers/FreeSql.Provider.QuestDb/Subtable/AutoSubtableAttribute.cs b/Providers/FreeSql.Provider.QuestDb/Subtable/AutoSubtableAttribute.cs new file mode 100644 index 00000000..7aad4b69 --- /dev/null +++ b/Providers/FreeSql.Provider.QuestDb/Subtable/AutoSubtableAttribute.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace FreeSql.Provider.QuestDb.Subtable +{ + [AttributeUsage(AttributeTargets.Property, Inherited = true)] + public class AutoSubtableAttribute : Attribute + { + public SubtableType? SubtableType + { + get; set; + } + public AutoSubtableAttribute(SubtableType type) + { + SubtableType = type; + } + } +} diff --git a/Providers/FreeSql.Provider.QuestDb/Subtable/SubtableType.cs b/Providers/FreeSql.Provider.QuestDb/Subtable/SubtableType.cs new file mode 100644 index 00000000..ccc6d73f --- /dev/null +++ b/Providers/FreeSql.Provider.QuestDb/Subtable/SubtableType.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace FreeSql.Provider.QuestDb.Subtable +{ + public enum SubtableType + { + Year = 1, + Month = 2, + Day = 3, + Hour = 4, + Second = 5, + Minute = 6, + Millisecond = 7, + Weekday = 8, + Quarter = 9 + } +} \ No newline at end of file diff --git a/Providers/FreeSql.Provider.QuestDb/key.snk b/Providers/FreeSql.Provider.QuestDb/key.snk new file mode 100644 index 00000000..be114935 Binary files /dev/null and b/Providers/FreeSql.Provider.QuestDb/key.snk differ