protected virtual string CompileCondition(WhereCondition condition, SqlCompilerContext context) { return(condition switch { CompareColumnsCondition compareColumnsCondition => CompileCondition(compareColumnsCondition, context), CompareCondition compareCondition => CompileCondition(compareCondition, context), InCondition inCondition => CompileCondition(inCondition, context), NestedCondition nestedCondition => CompileCondition(nestedCondition, context), RawCondition rawCondition => CompileCondition(rawCondition, context), _ => throw new ArgumentOutOfRangeException(nameof(condition)) });
/// <summary> /// /// </summary> /// <param name="node"></param> /// <param name="context"></param> /// <param name="batchScope"></param> /// <returns></returns> /// <exception cref="ArgumentNullException"></exception> public virtual SqlResult Compile(Node node, IResolveFieldContext context, IEnumerable?batchScope = null) { if (node == null) { throw new ArgumentNullException(nameof(node)); } if (context == null) { throw new ArgumentNullException(nameof(context)); } // TODO: Should we validate the node? var selections = new List <string>(); var tables = new List <string>(); var wheres = new List <WhereCondition>(); var orders = new List <string>(); var sqlCompilerContext = new SqlCompilerContext(this); StringifySqlAst(null, node, Array.Empty <string>(), context, selections, tables, wheres, orders, batchScope ?? Enumerable.Empty <object>(), sqlCompilerContext); var sb = new StringBuilder(); sb.AppendLine("SELECT"); sb.Append(" "); sb.AppendLine(string.Join(",\n ", selections)); sb.AppendLine(string.Join("\n", tables)); if (wheres.Count > 0) { sb.Append("WHERE "); sb.AppendLine(_dialect.CompileConditions(wheres, sqlCompilerContext)); } if (orders.Count > 0) { sb.Append("ORDER BY "); sb.AppendLine(string.Join(", ", orders)); } return(new SqlResult(sb.ToString().Trim(), sqlCompilerContext.Parameters)); }
public override void HandleBatchedManyToManyPaginated(Node?parent, SqlTable node, IReadOnlyDictionary <string, object> arguments, IResolveFieldContext context, ICollection <string> tables, IEnumerable <object> batchScope, SqlCompilerContext compilerContext, string joinCondition) => throw new NotSupportedException();
/// <inheritdoc /> public override void HandlePaginationAtRoot(Node?parent, SqlTable node, IReadOnlyDictionary <string, object> arguments, IResolveFieldContext context, ICollection <string> tables, SqlCompilerContext compilerContext) => throw new NotSupportedException();
/// <inheritdoc /> public override void HandleJoinedOneToManyPaginated(SqlTable parent, SqlTable node, IReadOnlyDictionary <string, object> arguments, IResolveFieldContext context, ICollection <string> tables, SqlCompilerContext compilerContext, string?joinCondition) => throw new NotSupportedException();
/// <inheritdoc /> public virtual string CompileConditions(IEnumerable <WhereCondition> conditions, SqlCompilerContext context) { if (conditions == null) { throw new ArgumentNullException(nameof(conditions)); } if (context == null) { throw new ArgumentNullException(nameof(context)); } var result = new StringBuilder(); using var enumerator = conditions.GetEnumerator(); var i = 0; while (enumerator.MoveNext()) { var condition = enumerator.Current; var compiled = CompileCondition(condition, context); if (string.IsNullOrEmpty(compiled)) { continue; } var boolOperator = i == 0 ? "" : condition.IsOr ? " OR " : " AND "; result.AppendFormat("{0}{1}", boolOperator, compiled); i++; } return(result.ToString()); }
/// <inheritdoc /> public abstract void HandleBatchedManyToManyPaginated(Node?parent, SqlTable node, IReadOnlyDictionary <string, object> arguments, IResolveFieldContext context, ICollection <string> tables, IEnumerable <object> batchScope, SqlCompilerContext compilerContext, string joinCondition);
/// <inheritdoc /> public abstract void HandlePaginationAtRoot(Node?parent, SqlTable node, IReadOnlyDictionary <string, object> arguments, IResolveFieldContext context, ICollection <string> tables, SqlCompilerContext compilerContext);
/// <inheritdoc /> public abstract void HandleJoinedOneToManyPaginated(SqlTable parent, SqlTable node, IReadOnlyDictionary <string, object> arguments, IResolveFieldContext context, ICollection <string> tables, SqlCompilerContext compilerContext, string?joinCondition);
private void StringifySqlAst(Node?parent, Node node, IReadOnlyCollection <string> prefix, IResolveFieldContext context, ICollection <string> selections, ICollection <string> tables, ICollection <WhereCondition> wheres, ICollection <string> orders, IEnumerable batchScope, SqlCompilerContext compilerContext) { switch (node) { case SqlTable sqlTable: HandleTable(parent, sqlTable, prefix, context, selections, tables, wheres, orders, batchScope, compilerContext); if (ThisIsNotTheEndOfThisBatch(parent, sqlTable)) { foreach (var child in sqlTable.Children) { StringifySqlAst(node, child, new List <string>(prefix) { sqlTable.As }, context, selections, tables, wheres, orders, batchScope, compilerContext); } } break; case SqlColumn sqlColumn: { if (!(parent is SqlTable table)) { throw new ArgumentException($"'{nameof(parent)}' must be of type {typeof(SqlTable)}", nameof(parent)); } var parentTable = table.As; string columnName; if (sqlColumn.Expression != null) { columnName = sqlColumn.Expression(_dialect.Quote(parentTable), sqlColumn.Arguments, context, table); } else if (table.ColumnExpression != null) { columnName = table.ColumnExpression(_dialect.Quote(parentTable), sqlColumn.Name, sqlColumn.Arguments, context); } else { columnName = $"{_dialect.Quote(parentTable)}.{_dialect.Quote(sqlColumn.Name)}"; } selections.Add($"{columnName} AS {_dialect.Quote(JoinPrefix(prefix) + sqlColumn.As)}"); break; } case SqlComposite sqlComposite: { if (!(parent is SqlTable table)) { throw new ArgumentException($"'{nameof(parent)}' must be of type {typeof(SqlTable)}", nameof(parent)); } var parentTable = table.As; selections.Add($"{_dialect.CompositeKey(parentTable, sqlComposite.Name)} AS {_dialect.Quote(JoinPrefix(prefix) + sqlComposite.As)}"); break; } case SqlJunction _: case SqlNoop _: break; default: throw new ArgumentOutOfRangeException(nameof(node), $"Don't know how to handle {node.GetType()}."); } }
private void HandleTable(Node?parent, SqlTable node, IEnumerable <string> prefix, IResolveFieldContext context, ICollection <string> selections, ICollection <string> tables, ICollection <WhereCondition> wheres, ICollection <string> orders, IEnumerable batchScope, SqlCompilerContext compilerContext) { var arguments = node.Arguments; // also check for batching if (node.Paginate == false && (node.Batch == null || node.Junction?.Batch == null || parent == null)) { if (node.Junction?.Where != null) { var whereBuilder = new WhereBuilder(_dialect.Quote(node.Junction.As), wheres); node.Junction.Where.Invoke(whereBuilder, arguments, context, node); } // only add the where clause if there's no join or the join is not paginated if (node.Where != null) { var whereBuilder = new WhereBuilder(_dialect.Quote(node.As), wheres); node.Where.Invoke(whereBuilder, arguments, context, node); } } if (ThisIsNotTheEndOfThisBatch(parent, node)) { if (node.Junction?.OrderBy != null) { var junctionOrderBy = _dialect.CompileOrderBy(node.Junction.OrderBy); orders.Add(junctionOrderBy); } if (node.OrderBy != null) { var orderBy = _dialect.CompileOrderBy(node.OrderBy); orders.Add(orderBy); } // if (node.Junction?.SortKey != null) // { // var junctionOrderBy = _dialect.CompileOrderBy(node.Junction.SortKey); // orders.Add(junctionOrderBy); // } // // if (node.SortKey != null) // { // var orderBy = _dialect.CompileOrderBy(node.SortKey); // orders.Add(orderBy); // } } if (node.Join != null) { if (parent is SqlTable parentTable) { var join = new JoinBuilder(_dialect.Quote(parentTable.Name), _dialect.Quote(parentTable.As), _dialect.Quote(node.Name), _dialect.Quote(node.As)); node.Join(join, arguments, context, node); if (node.Paginate) { _dialect.HandleJoinedOneToManyPaginated(parentTable, node, arguments, context, tables, compilerContext, join.Condition == null ? null : _dialect.CompileConditions(new[] { join.Condition }, compilerContext)); } else if (join.Condition is RawCondition) { tables.Add($"{join.From} ON {_dialect.CompileConditions(new[] {join.Condition}, compilerContext)}"); } else if (join.Condition != null) { tables.Add($"LEFT JOIN {_dialect.Quote(node.Name)} {_dialect.Quote(node.As)} ON {_dialect.CompileConditions(new[] {join.Condition}, compilerContext)}"); } return; } } else if (node.Junction?.Batch != null) { if (parent is SqlTable parentTable) { string columnName; if (node.Junction.Batch.ParentKey.Expression != null) { columnName = node.Junction.Batch.ParentKey.Expression(_dialect.Quote(parentTable.As), node.Junction.Batch.ParentKey.Arguments, context, node); } else { columnName = $"{_dialect.Quote(parentTable.As)}.{_dialect.Quote(node.Junction.Batch.ParentKey.Name)}"; } selections.Add($"{columnName} AS ${_dialect.Quote(JoinPrefix(prefix) + node.Junction.Batch.ParentKey.As)}"); } else { var join = new JoinBuilder(_dialect.Quote(node.Name), _dialect.Quote(node.As), _dialect.Quote(node.Junction.Table), _dialect.Quote(node.Junction.As)); if (node.Junction.Batch.Join != null) { node.Junction.Batch.Join(join, arguments, context, node); } if (join.Condition == null) { throw new JoinMonsterException($"The 'batch' join condition on table '{node.Name}' for junction '{node.Junction.Table}' cannot be null."); } var joinCondition = _dialect.CompileConditions(new[] { join.Condition }, compilerContext); if (node.Paginate) { _dialect.HandleBatchedManyToManyPaginated(parent, node, arguments, context, tables, batchScope.Cast <object>(), compilerContext, joinCondition); } else { tables.Add($"FROM {_dialect.Quote(node.Junction.Table)} {_dialect.Quote(node.Junction.As)}"); tables.Add($"LEFT JOIN {_dialect.Quote(node.Name)} {_dialect.Quote(node.As)} ON {joinCondition}"); var column = _dialect.Quote(node.Junction.Batch.ThisKey.Name); var whereBuilder = new WhereBuilder(_dialect.Quote(node.Junction.As), wheres); if (node.Junction.Batch.Where != null) { node.Junction.Batch.Where(whereBuilder, column, batchScope, arguments, context, node); } else { whereBuilder.In(column, batchScope); } } } } else if (node.Junction?.FromParent != null && node.Junction?.ToChild != null) { if (parent is SqlTable parentTable) { // TODO: Handle batching and paging var fromParentJoin = new JoinBuilder(_dialect.Quote(parentTable.Name), _dialect.Quote(parentTable.As), _dialect.Quote(node.Junction.Table), _dialect.Quote(node.Junction.As)); var toChildJoin = new JoinBuilder(_dialect.Quote(parentTable.Name), _dialect.Quote(node.Junction.As), _dialect.Quote(node.Name), _dialect.Quote(node.As)); node.Junction.FromParent(fromParentJoin, arguments, context, node); node.Junction.ToChild(toChildJoin, arguments, context, node); if (fromParentJoin.Condition == null) { throw new JoinMonsterException($"The 'fromParent' join condition on table '{node.Name}' for junction '{node.Junction.Table}' cannot be null."); } if (toChildJoin.Condition == null) { throw new JoinMonsterException($"The 'toChild' join condition on table '{node.Name}' for junction '{node.Junction.Table}' cannot be null."); } var compiledFromParentJoinCondition = _dialect.CompileConditions(new[] { fromParentJoin.Condition }, compilerContext); var compiledToChildJoinCondition = _dialect.CompileConditions(new[] { toChildJoin.Condition }, compilerContext); if (node.Paginate) { // _dialect.HandleJoinedManyToManyPaginated(); } else { if (fromParentJoin.Condition is RawCondition) { tables.Add($"{fromParentJoin.From} ON {compiledFromParentJoinCondition}"); } else { tables.Add( $"LEFT JOIN {_dialect.Quote(node.Junction.Table)} {_dialect.Quote(node.Junction.As)} ON {compiledFromParentJoinCondition}"); } if (toChildJoin.Condition is RawCondition) { tables.Add($"{toChildJoin.From} ON {compiledToChildJoinCondition}"); } else { tables.Add($"LEFT JOIN {_dialect.Quote(node.Name)} {_dialect.Quote(node.As)} ON {compiledToChildJoinCondition}"); } } } return; } else if (node.Batch != null) { if (parent is SqlTable parentTable) { string columnName; if (node.Batch.ParentKey.Expression != null) { columnName = node.Batch.ParentKey.Expression(_dialect.Quote(parentTable.As), node.Batch.ParentKey.Arguments, context, node); } else { columnName = $"{_dialect.Quote(parentTable.As)}.{_dialect.Quote(node.Batch.ParentKey.Name)}"; } selections.Add($"{columnName} AS {_dialect.Quote(JoinPrefix(prefix) + node.Batch.ParentKey.As)}"); } else if (node.Paginate) { _dialect.HandleBatchedOneToManyPaginated(parent, node, arguments, context, tables, batchScope.Cast <object>(), compilerContext); } else { tables.Add($"FROM {_dialect.Quote(node.Name)} AS {_dialect.Quote(node.As)}"); var column = _dialect.Quote(node.Batch.ThisKey.Name); var whereBuilder = new WhereBuilder(_dialect.Quote(node.As), wheres); if (node.Batch.Where != null) { node.Batch.Where(whereBuilder, column, batchScope, arguments, context, node); } else { whereBuilder.In(column, batchScope); } } return; } else if (node.Paginate) { _dialect.HandlePaginationAtRoot(parent, node, arguments, context, tables, compilerContext); return; } tables.Add($"FROM {_dialect.Quote(node.Name)} AS {_dialect.Quote(node.As)}"); }
/// <inheritdoc /> public override void HandleJoinedOneToManyPaginated(SqlTable parent, SqlTable node, IReadOnlyDictionary <string, object> arguments, IResolveFieldContext context, ICollection <string> tables, SqlCompilerContext compilerContext, string?joinCondition) { if (parent == null) { throw new ArgumentNullException(nameof(parent)); } if (node == null) { throw new ArgumentNullException(nameof(node)); } if (arguments == null) { throw new ArgumentNullException(nameof(arguments)); } if (context == null) { throw new ArgumentNullException(nameof(context)); } if (tables == null) { throw new ArgumentNullException(nameof(tables)); } if (node.Join == null) { throw new JoinMonsterException($"{nameof(node)}.{nameof(node.Join)} on table '{node.Name}' cannot be null."); } var join = new JoinBuilder(Quote(parent.Name), Quote(parent.As), Quote(node.Name), Quote(node.As)); node.Join(join, arguments, context, node); if (join.Condition == null) { throw new JoinMonsterException($"The join condition on table '{node.Name}' cannot be null."); } var pagingWhereConditions = new List <WhereCondition> { join.Condition }; if (node.Where != null) { var whereBuilder = new WhereBuilder(Quote(node.As), pagingWhereConditions); node.Where.Invoke(whereBuilder, arguments, context, node); } if (node.SortKey != null) { var(limit, order, whereCondition) = InterpretForKeysetPaging(node, arguments, context); if (whereCondition != null) { pagingWhereConditions.Add(whereCondition); } tables.Add(KeysetPagingSelect(node.Name, pagingWhereConditions, order, limit, node.As, joinCondition, "LEFT", compilerContext)); } else if (node.OrderBy != null) { var(limit, offset, order) = InterpretForOffsetPaging(node, arguments, context); tables.Add(OffsetPagingSelect(node.Name, pagingWhereConditions, order, limit, offset, node.As, joinCondition, "LEFT", compilerContext)); } else { throw new JoinMonsterException("Cannot paginate without a SortKey or an OrderBy clause."); } }
public override void HandleBatchedManyToManyPaginated(Node?parent, SqlTable node, IReadOnlyDictionary <string, object> arguments, IResolveFieldContext context, ICollection <string> tables, IEnumerable <object> batchScope, SqlCompilerContext compilerContext, string joinCondition) { if (node == null) { throw new ArgumentNullException(nameof(node)); } if (arguments == null) { throw new ArgumentNullException(nameof(arguments)); } if (context == null) { throw new ArgumentNullException(nameof(context)); } if (tables == null) { throw new ArgumentNullException(nameof(tables)); } if (batchScope == null) { throw new ArgumentNullException(nameof(batchScope)); } if (compilerContext == null) { throw new ArgumentNullException(nameof(compilerContext)); } if (joinCondition == null) { throw new ArgumentNullException(nameof(joinCondition)); } if (node.Junction == null) { throw new InvalidOperationException("node.Junction cannot be null."); } if (node.Junction.Batch == null) { throw new InvalidOperationException("node.Junction.Batch cannot be null."); } // var thisKeyOperand = GenerateCastExpressionFromValueType( // $"${node.Junction.As}.{node.Junction.Batch.ThisKey.Name}", // batchScope.ElementAtOrDefault(0) // ); var thisKeyOperand = $"${node.Junction.As}.{node.Junction.Batch.ThisKey.Name}"; var whereConditions = new List <WhereCondition> { new RawCondition($"{thisKeyOperand} = temp.\"{node.Junction.Batch.ParentKey.Name}\"", null) }; if (node.Junction.Where != null) { var whereBuilder = new WhereBuilder(node.Junction.As, whereConditions); node.Junction.Where(whereBuilder, arguments, context, node); } if (node.Where != null) { var whereBuilder = new WhereBuilder(node.As, whereConditions); node.Where(whereBuilder, arguments, context, node); } var tempTable = $"FROM (VALUES {string.Join("", batchScope.Select(val => $"({val})"))}) temp(\"{node.Junction.Batch.ParentKey.Name}\")"; tables.Add(tempTable); var lateralJoinCondition = $"{thisKeyOperand} = temp.\"{node.Junction.Batch.ParentKey.Name}\""; string?extraJoin = null; if (node.Where != null || node.OrderBy != null) { extraJoin = $"LEFT JOIN {node.Name} {Quote(node.As)} ON {joinCondition}"; } if (node.SortKey != null || node.Junction.SortKey != null) { var(limit, order, whereCondition) = InterpretForKeysetPaging(node, arguments, context); whereConditions.Add(whereCondition); tables.Add(KeysetPagingSelect(node.Junction.Table, whereConditions, order, limit, node.Junction.As, lateralJoinCondition, "LEFT", compilerContext)); } else if (node.OrderBy != null || node.Junction.OrderBy != null) { var(limit, offset, order) = InterpretForOffsetPaging(node, arguments, context); tables.Add(OffsetPagingSelect(node.Junction.Table, whereConditions, order, limit, offset, node.Junction.As, joinCondition, "LEFT", compilerContext, extraJoin)); } tables.Add($"LEFT JOIN {node.Name} AS \"{node.As}\" ON {joinCondition}"); }
public override void HandleBatchedOneToManyPaginated(Node?parent, SqlTable node, IReadOnlyDictionary <string, object> arguments, IResolveFieldContext context, ICollection <string> tables, IEnumerable <object> batchScope, SqlCompilerContext compilerContext) { if (node == null) { throw new ArgumentNullException(nameof(node)); } if (arguments == null) { throw new ArgumentNullException(nameof(arguments)); } if (context == null) { throw new ArgumentNullException(nameof(context)); } if (tables == null) { throw new ArgumentNullException(nameof(tables)); } if (batchScope == null) { throw new ArgumentNullException(nameof(batchScope)); } if (compilerContext == null) { throw new ArgumentNullException(nameof(compilerContext)); } if (node.Batch == null) { throw new InvalidOperationException("node.Batchcannot be null."); } var thisKeyOperand = $"${node.As}.{node.Batch.ThisKey.Name}"; var whereConditions = new List <WhereCondition> { new RawCondition($"{thisKeyOperand} = temp.\"{node.Batch.ParentKey.Name}\"", null) }; if (node.Where != null) { var whereBuilder = new WhereBuilder(node.As, whereConditions); node.Where(whereBuilder, arguments, context, node); } var tempTable = $"FROM (VALUES {string.Join("", batchScope.Select(val => $"({val})"))}) temp(\"{node.Batch.ParentKey.Name}\")"; tables.Add(tempTable); var lateralJoinCondition = $"{thisKeyOperand} = temp.\"{node.Batch.ParentKey.Name}\""; if (node.SortKey != null) { var(limit, order, whereCondition) = InterpretForKeysetPaging(node, arguments, context); whereConditions.Add(whereCondition); tables.Add(KeysetPagingSelect(node.Name, whereConditions, order, limit, node.As, lateralJoinCondition, null, compilerContext)); } else if (node.OrderBy != null) { var(limit, offset, order) = InterpretForOffsetPaging(node, arguments, context); tables.Add(OffsetPagingSelect(node.Name, whereConditions, order, limit, offset, node.As, lateralJoinCondition, null, compilerContext)); } }
/// <inheritdoc /> public override void HandlePaginationAtRoot(Node?parent, SqlTable node, IReadOnlyDictionary <string, object> arguments, IResolveFieldContext context, ICollection <string> tables, SqlCompilerContext compilerContext) { var pagingWhereConditions = new List <WhereCondition>(); if (node.SortKey != null) { var(limit, order, whereCondition) = InterpretForKeysetPaging(node, arguments, context); if (whereCondition != null) { pagingWhereConditions.Add(whereCondition); } if (node.Where != null) { var whereBuilder = new WhereBuilder(Quote(node.As), pagingWhereConditions); node.Where.Invoke(whereBuilder, arguments, context, node); } tables.Add(KeysetPagingSelect(node.Name, pagingWhereConditions, order, limit, node.As, null, null, compilerContext)); } else if (node.OrderBy != null) { var(limit, offset, order) = InterpretForOffsetPaging(node, arguments, context); if (node.Where != null) { var whereBuilder = new WhereBuilder(Quote(node.As), pagingWhereConditions); node.Where.Invoke(whereBuilder, arguments, context, node); } tables.Add(OffsetPagingSelect(node.Name, pagingWhereConditions, order, limit, offset, node.As, null, null, compilerContext)); } }