Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[QP-6630] WHERE 조건문 내에서 사용한 column에 대한 순회 구현 #81

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Qsi.Debugger/MainWindow.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,10 @@
<Rectangle Margin="0,6" Fill="{DynamicResource ThemeBackgroundBrush}" Height="2" />
</TreeDataTemplate>

<TreeDataTemplate DataType="md:QsiLabelTreeItem">
<TextBlock Foreground="LightGray" Text="{Binding Label}"></TextBlock>
</TreeDataTemplate>

<TreeDataTemplate DataType="md:QsiColumnTreeItem" ItemsSource="{Binding Items}">
<StackPanel Orientation="Horizontal">
<TextBlock Foreground="{Binding Converter={cvt:ColumnBrushConverter}}"
Expand Down
9 changes: 8 additions & 1 deletion Qsi.Debugger/MainWindow.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -457,9 +457,16 @@ private void BuildQsiTableTree(IList<QsiTableStructure> tables)
items.Add(new QsiSplitTreeItem());

items.AddRange(tables[i].Columns.Select(c => new QsiColumnTreeItem(c)));

// Indirect columns
if (tables[i].IndirectColumns is not { } indirectColumns)
continue;

items.Add(new QsiLabelTreeItem("Indirect Columns"));
items.AddRange(indirectColumns.Select(c => new QsiColumnTreeItem(c)));
}

_tvResult.Items = items;
}
#endregion
}
}
11 changes: 11 additions & 0 deletions Qsi.Debugger/Models/QsiLabelTreeItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Qsi.Debugger.Models;

public class QsiLabelTreeItem
{
public string Label { get; }

public QsiLabelTreeItem(string label)
{
Label = label;
}
}
181 changes: 181 additions & 0 deletions Qsi/Analyzers/Expression/ColumnResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Qsi.Analyzers.Extensions;
using Qsi.Analyzers.Table.Context;
using Qsi.Data;
using Qsi.Extensions;
using Qsi.Services;
using Qsi.Shared.Extensions;
using Qsi.Tree;

namespace Qsi.Analyzers.Expression;

public class ColumnResolver : ExpressionResolver<QsiTableColumn>
{
public delegate ValueTask<QsiTableStructure> BuildTableStructureDelegate(TableCompileContext context, IQsiTableNode table);

private const string scopeFieldList = "field list";

protected readonly IQsiLanguageService _languageService;
protected readonly BuildTableStructureDelegate _buildTableStructureDelegate;

public ColumnResolver(IQsiLanguageService languageService, BuildTableStructureDelegate buildTableStructureDelegate)
{
_languageService = languageService;
_buildTableStructureDelegate = buildTableStructureDelegate;
}

protected override IEnumerable<QsiTableColumn> ResolveTableExpression(TableCompileContext context, IQsiTableExpressionNode expression)
{
using var scopedContext = new TableCompileContext(context);
var structure = _buildTableStructureDelegate(scopedContext, expression.Table).Result;

foreach (var c in structure.Columns)
yield return c;
}

protected override IEnumerable<QsiTableColumn> ResolveColumnExpression(TableCompileContext context, IQsiColumnExpressionNode expression)
{
switch (expression.Column)
{
case IQsiAllColumnNode _ when
expression.FindDescendant<IQsiParametersExpressionNode, IQsiInvokeExpressionNode>(out _, out var i) &&
i.Member != null &&
i.Member.Identifier.Level == 1 &&
i.Member.Identifier[0].Value.EqualsIgnoreCase("COUNT"):
yield break;

case IQsiAllColumnNode allColumnNode:
{
foreach (var column in ResolveAllColumns(context, allColumnNode, true))
yield return column;

break;
}

case IQsiColumnReferenceNode columnReferenceNode:
foreach (var column in ResolveColumnReference(context, columnReferenceNode, out _))
yield return column;

break;

default:
throw new InvalidOperationException();
}
}

private IEnumerable<QsiTableColumn> ResolveAllColumns(TableCompileContext context, IQsiAllColumnNode column, bool includeInvisible)
{
context.ThrowIfCancellationRequested();

includeInvisible |= column.IncludeInvisibleColumns;

// *
if (column.Path == null)
{
if (context.SourceTable == null)
throw new QsiException(QsiError.NoTablesUsed);

return includeInvisible ?
context.SourceTable.Columns :
context.SourceTable.VisibleColumns;
}

// path.or.alias.*

IEnumerable<QsiTableStructure> tables = LookupDataTableStructuresInExpression(context, column.Path);

if (!tables.Any())
throw new QsiException(QsiError.UnknownTable, column.Path);

return tables.SelectMany(t => includeInvisible ? t.Columns : t.VisibleColumns);
}

private IEnumerable<QsiTableColumn> ResolveColumnReference(TableCompileContext context, IQsiColumnReferenceNode column, out QsiQualifiedIdentifier implicitTableWildcardTarget)
{
context.ThrowIfCancellationRequested();

if (column.Name.Level == 0)
throw new QsiException(QsiError.UnknownColumnIn, "null", scopeFieldList);

IEnumerable<TableCompileContext> candidateContexts =
context.AnalyzerOptions.UseOuterQueryColumn ? context.AncestorsAndSelf() : new[] { context };

var lastName = column.Name[^1];

foreach (var candidateContext in candidateContexts)
{
if (candidateContext.SourceTable == null)
continue;

IEnumerable<QsiTableStructure> candidateSourceTables;

if (column.Name.Level > 1)
{
var identifier = new QsiQualifiedIdentifier(column.Name[..^1]);

candidateSourceTables = LookupDataTableStructuresInExpression(candidateContext, identifier);

if (!candidateSourceTables.Any())
continue;
}
else
{
candidateSourceTables = new[] { candidateContext.SourceTable };
}

QsiTableColumn[] columns = candidateSourceTables
.SelectMany(s => s.Columns.Where(c => _languageService.MatchIdentifier(c.Name, lastName) && c.IsVisible))
.Take(2)
.ToArray();

// If visible column is not exists, get invisible columns
if (columns.Length is 0)
{
columns = candidateSourceTables
.SelectMany(s => s.Columns.Where(c => _languageService.MatchIdentifier(c.Name, lastName) && !c.IsVisible))
.Take(2)
.ToArray();
}

switch (columns.Length)
{
case 0:
break;

case > 1:
throw new QsiException(QsiError.AmbiguousColumnIn, column.Name, scopeFieldList);

default:
implicitTableWildcardTarget = default;
return new[] { columns[0] };
}
}

// NOTE: 'SELECT actor FROM actor' same as
// 'SELECT actor.* FROM actor'
if (context.AnalyzerOptions.UseImplicitTableWildcardInSelect)
return ImplicitlyResolveColumnReference(context, column, out implicitTableWildcardTarget);

throw new QsiException(QsiError.UnknownColumnIn, lastName.Value, scopeFieldList);
}

private IEnumerable<QsiTableStructure> LookupDataTableStructuresInExpression(TableCompileContext context, QsiQualifiedIdentifier identifier)
{
context.ThrowIfCancellationRequested();
return context.GetAllSourceTables().Where(x => _languageService.MatchIdentifier(context, x, identifier));
}

private QsiTableColumn[] ImplicitlyResolveColumnReference(TableCompileContext context, IQsiColumnReferenceNode column, out QsiQualifiedIdentifier implicitTableWildcardTarget)
{
QsiTableStructure[] implicitSources = LookupDataTableStructuresInExpression(context, column.Name).ToArray();

if (implicitSources.Length != 1)
throw new QsiException(QsiError.UnknownColumnIn, column.Name[^1], scopeFieldList);

implicitTableWildcardTarget = column.Name;
return implicitSources[0].Columns.ToArray();
}
}
Loading
Loading