public string ToSQLString(string Quotes, string paramPrefix, List <XSqlCommandParam> Params, ISqlCompiler compiler) { if (Params == null) { return(Name + "(" + string.Join(",", Arguments.Select(p => p.ToSQLString(Quotes, paramPrefix, Params, compiler))) + ")"); } else { return(compiler.Compile(this.Clone(), Params)); } throw new NotImplementedException(); }
/// <summary> /// Takes a <see cref="IResolveFieldContext"/> and returns a hydrated object with the data. /// </summary> /// <param name="context">The <see cref="IResolveFieldContext"/>.</param> /// <param name="databaseCall">A <see cref="DatabaseCallDelegate"/> that is passed the compiled SQL and calls the database and returns a <see cref="IDataReader"/>.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param> /// <returns>The correctly nested data from the database.</returns> /// <exception cref="ArgumentNullException">If <c>context</c> or <c>databaseCall</c> is null.</exception> public async Task <object?> ExecuteAsync(IResolveFieldContext context, DatabaseCallDelegate databaseCall, CancellationToken cancellationToken) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (databaseCall == null) { throw new ArgumentNullException(nameof(databaseCall)); } var sqlAst = _converter.Convert(context); var sqlResult = _compiler.Compile(sqlAst, context); var data = new List <Dictionary <string, object?> >(); using (var reader = await databaseCall(sqlResult.Sql, sqlResult.Parameters).ConfigureAwait(false)) { while (await reader.ReadAsync(cancellationToken)) { var item = new Dictionary <string, object?>(); for (var i = 0; i < reader.FieldCount; ++i) { var value = await reader.IsDBNullAsync(i, cancellationToken) ? null : await reader.GetFieldValueAsync <object>(i, cancellationToken); item[reader.GetName(i)] = value; } data.Add(item); } } var objectShape = _objectShaper.DefineObjectShape(sqlAst); #pragma warning disable 8620 var nested = _hydrator.Nest(data, objectShape); var result = _arrayToConnectionConverter.Convert(nested, sqlAst, context); #pragma warning restore 8620 if (result == null) { return(null); } await _batchPlanner.NextBatch(sqlAst, result, databaseCall, context, cancellationToken).ConfigureAwait(false); return(result); }
private async Task NextBatchChild(SqlTable sqlTable, object?data, DatabaseCallDelegate databaseCall, IResolveFieldContext context, CancellationToken cancellationToken) { var fieldName = sqlTable.FieldName; data = data switch { List <object> objList => objList.Select(x => (IDictionary <string, object?>)x), Connection <object> connection => connection.Items.Select(x => (IDictionary <string, object?>)x), _ => data }; // see if any begin a new batch if (sqlTable.Batch != null || sqlTable.Junction?.Batch != null) { string thisKey = null !; string parentKey = null !; if (sqlTable.Batch != null) { // if so, we know we'll need to get the key for matching with the parent key sqlTable.AddColumn(sqlTable.Batch.ThisKey); thisKey = sqlTable.Batch.ThisKey.FieldName; parentKey = sqlTable.Batch.ParentKey.FieldName; } else if (sqlTable.Junction?.Batch != null) { sqlTable.AddColumn(sqlTable.Junction.Batch.ThisKey); thisKey = sqlTable.Junction.Batch.ThisKey.FieldName; parentKey = sqlTable.Junction.Batch.ParentKey.FieldName; } if (data is IEnumerable <IDictionary <string, object?> > entries) { // the "batch scope" is the set of values to match this key against from the previous batch var batchScope = new List <object>(); var entryList = entries.ToList(); foreach (var entry in entryList) { var values = PrepareValues(entry, parentKey); batchScope.AddRange(values); } if (batchScope.Count == 0) { return; } // generate the SQL, with the batch scope values incorporated in a WHERE IN clause var sqlResult = _compiler.Compile(sqlTable, context, SqlDialect.CastArray(batchScope)); var objectShape = _objectShaper.DefineObjectShape(sqlTable); // grab the data var newData = await HandleDatabaseCall(databaseCall, sqlResult, objectShape, cancellationToken); // group the rows by the key so we can match them with the previous batch var newDataGrouped = newData.GroupBy(x => x[thisKey]) .ToDictionary(x => x.Key, x => x.ToList()); // but if we paginate, we must convert to connection type first if (sqlTable.Paginate) { //TODO: implement // foreach (var group in newDataGrouped) // newDataGrouped[group.Key] = // (List<Dictionary<string, object?>>) // _arrayToConnectionConverter.Convert(group.Value, sqlTable, context); } // if we they want many rows, give them an array if (sqlTable.GrabMany) { foreach (var entry in entryList) { var values = PrepareValues(entry, parentKey); var res = new List <Dictionary <string, object?> >(); foreach (var value in values) { if (newDataGrouped.TryGetValue(value, out var obj)) { res.AddRange(obj); } } entry[fieldName] = res; } } else { var matchedData = new List <object>(); foreach (var entry in entryList) { if (entry.TryGetValue(parentKey, out var key) == false) { continue; } if (newDataGrouped.TryGetValue(key, out var list) && list.Count > 0) { entry[fieldName] = _arrayToConnectionConverter.Convert(list[0], sqlTable, context); matchedData.Add(entry); } else { entry[fieldName] = null; } } data = matchedData; } switch (data) { case IEnumerable <IDictionary <string, object?> > list: { var nextLevelData = list .Where(x => x.Count > 0) .Select(x => { if (x.TryGetValue(fieldName, out var value) && value is List <Dictionary <string, object?> > dict) { return(dict); } return(null); }) .Where(x => x is { Count: > 0 }) .SelectMany(x => x) .Select(x => x.ToDictionary()) .ToList(); await NextBatch(sqlTable, nextLevelData, databaseCall, context, cancellationToken); return; }