812 lines
33 KiB
C#
812 lines
33 KiB
C#
#nullable enable
|
|
|
|
using System.Data;
|
|
using System.Globalization;
|
|
using MBN_STOCK_WEBVIEW.Core.Playout.Scenes;
|
|
using MBN_STOCK_WEBVIEW.Infrastructure;
|
|
using MMoneyCoderSharp.Data;
|
|
|
|
internal static class RealSceneDbSmoke
|
|
{
|
|
public static async Task RunAsync(
|
|
IDataQueryExecutor executor,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(executor);
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
var traced = new QueryTracingDataQueryExecutor(executor);
|
|
var run = new SceneSmokeRun();
|
|
|
|
Console.WriteLine("DB_SMOKE_STEP: bounded scene selectors");
|
|
var pieName = await SelectOneAsync(
|
|
traced,
|
|
"SELECTOR_INPUT_PIE",
|
|
"SELECT STOCK_NAME FROM INPUT_PIE WHERE STOCK_NAME IS NOT NULL AND ROWNUM = 1",
|
|
"STOCK_NAME",
|
|
cancellationToken).ConfigureAwait(false);
|
|
var profitName = await SelectOneAsync(
|
|
traced,
|
|
"SELECTOR_INPUT_PROFIT",
|
|
"SELECT STOCK_NAME FROM INPUT_PROFIT WHERE STOCK_NAME IS NOT NULL AND ROWNUM = 1",
|
|
"STOCK_NAME",
|
|
cancellationToken).ConfigureAwait(false);
|
|
var growthName = await SelectOneAsync(
|
|
traced,
|
|
"SELECTOR_INPUT_GROW",
|
|
"SELECT STOCK_NAME FROM INPUT_GROW WHERE STOCK_NAME IS NOT NULL AND ROWNUM = 1",
|
|
"STOCK_NAME",
|
|
cancellationToken).ConfigureAwait(false);
|
|
var salesName = await SelectOneAsync(
|
|
traced,
|
|
"SELECTOR_INPUT_SELL",
|
|
"SELECT STOCK_NAME FROM INPUT_SELL WHERE STOCK_NAME IS NOT NULL AND ROWNUM = 1",
|
|
"STOCK_NAME",
|
|
cancellationToken).ConfigureAwait(false);
|
|
var nxtStockCode = await SelectOneAsync(
|
|
traced,
|
|
DataSourceKind.MariaDb,
|
|
"SELECTOR_NXT_STOCK",
|
|
"""
|
|
SELECT a.f_stock_code STOCK_CODE
|
|
FROM n_online a
|
|
JOIN n_stock b ON a.f_stock_code = b.f_stock_code
|
|
JOIN n_batch_his c ON a.f_stock_code = c.f_stock_code
|
|
WHERE a.f_curr_price <> 0
|
|
AND c.f_data_day = (
|
|
SELECT MAX(h.f_data_day)
|
|
FROM n_batch_his h
|
|
WHERE h.f_stock_code = a.f_stock_code)
|
|
LIMIT 1
|
|
""",
|
|
"STOCK_CODE",
|
|
cancellationToken).ConfigureAwait(false);
|
|
var domesticStock = await SelectStockAsync(
|
|
traced,
|
|
"SELECTOR_DOMESTIC_STOCK",
|
|
"""
|
|
SELECT STOCK_CODE, STOCK_NAME
|
|
FROM (
|
|
SELECT a.f_stock_code STOCK_CODE, b.f_stock_wanname STOCK_NAME
|
|
FROM t_online1 a
|
|
JOIN t_stock b ON a.f_stock_code = b.f_stock_code
|
|
WHERE a.f_curr_price <> 0
|
|
AND b.f_mkt_halt = 'N'
|
|
AND b.f_stock_wanname IS NOT NULL
|
|
ORDER BY a.f_stock_code
|
|
)
|
|
WHERE ROWNUM = 1
|
|
""",
|
|
cancellationToken).ConfigureAwait(false);
|
|
var comparisonNames = await SelectManyAsync(
|
|
traced,
|
|
"SELECTOR_COMPARISON_STOCKS",
|
|
"""
|
|
SELECT STOCK_NAME
|
|
FROM (
|
|
SELECT b.f_stock_wanname STOCK_NAME
|
|
FROM t_online1 a
|
|
JOIN t_stock b ON a.f_stock_code = b.f_stock_code
|
|
WHERE a.f_curr_price <> 0
|
|
AND b.f_mkt_halt = 'N'
|
|
AND b.f_stock_wanname IS NOT NULL
|
|
AND EXISTS (
|
|
SELECT 1
|
|
FROM t_candle_history h
|
|
WHERE h.f_stock_code = a.f_stock_code)
|
|
ORDER BY a.f_stock_code
|
|
)
|
|
WHERE ROWNUM <= 2
|
|
""",
|
|
"STOCK_NAME",
|
|
maximumRows: 2,
|
|
cancellationToken).ConfigureAwait(false);
|
|
var dealerStockName = await SelectOneAsync(
|
|
traced,
|
|
"SELECTOR_DEALER_STOCK",
|
|
"""
|
|
SELECT STOCK_NAME
|
|
FROM (
|
|
SELECT b.f_stock_wanname STOCK_NAME
|
|
FROM t_online1 a
|
|
JOIN t_stock b ON a.f_stock_code = b.f_stock_code
|
|
JOIN t_dealer d ON a.f_stock_code = d.f_stock_code
|
|
WHERE a.f_curr_price <> 0
|
|
AND b.f_mkt_halt = 'N'
|
|
AND d.f_buy_dealer_no_1 IN (SELECT f_sale_no FROM t_sale_member)
|
|
AND d.f_buy_dealer_no_2 IN (SELECT f_sale_no FROM t_sale_member)
|
|
AND d.f_buy_dealer_no_3 IN (SELECT f_sale_no FROM t_sale_member)
|
|
AND d.f_buy_dealer_no_4 IN (SELECT f_sale_no FROM t_sale_member)
|
|
AND d.f_buy_dealer_no_5 IN (SELECT f_sale_no FROM t_sale_member)
|
|
AND d.f_sell_dealer_no_1 IN (SELECT f_sale_no FROM t_sale_member)
|
|
AND d.f_sell_dealer_no_2 IN (SELECT f_sale_no FROM t_sale_member)
|
|
AND d.f_sell_dealer_no_3 IN (SELECT f_sale_no FROM t_sale_member)
|
|
AND d.f_sell_dealer_no_4 IN (SELECT f_sale_no FROM t_sale_member)
|
|
AND d.f_sell_dealer_no_5 IN (SELECT f_sale_no FROM t_sale_member)
|
|
ORDER BY a.f_stock_code
|
|
)
|
|
WHERE ROWNUM = 1
|
|
""",
|
|
"STOCK_NAME",
|
|
cancellationToken).ConfigureAwait(false);
|
|
var orderBookStockCode = await SelectOneAsync(
|
|
traced,
|
|
"SELECTOR_ORDER_BOOK_STOCK",
|
|
"""
|
|
SELECT STOCK_CODE
|
|
FROM (
|
|
SELECT a.f_stock_code STOCK_CODE
|
|
FROM t_online1 a
|
|
JOIN t_stock b ON a.f_stock_code = b.f_stock_code
|
|
JOIN t_online1_call c ON a.f_stock_code = c.f_stock_code
|
|
WHERE a.f_curr_price > 0
|
|
AND b.f_mkt_halt = 'N'
|
|
AND c.f_call_sell_1 IS NOT NULL
|
|
AND c.f_call_sell_2 IS NOT NULL
|
|
AND c.f_call_sell_3 IS NOT NULL
|
|
AND c.f_call_sell_jan_1 IS NOT NULL
|
|
AND c.f_call_sell_jan_2 IS NOT NULL
|
|
AND c.f_call_sell_jan_3 IS NOT NULL
|
|
AND c.f_call_buy_1 IS NOT NULL
|
|
AND c.f_call_buy_2 IS NOT NULL
|
|
AND c.f_call_buy_3 IS NOT NULL
|
|
AND c.f_call_buy_jan_1 IS NOT NULL
|
|
AND c.f_call_buy_jan_2 IS NOT NULL
|
|
AND c.f_call_buy_jan_3 IS NOT NULL
|
|
AND c.f_t_call_sell_jan IS NOT NULL
|
|
AND c.f_t_call_buy_jan IS NOT NULL
|
|
ORDER BY a.f_stock_code
|
|
)
|
|
WHERE ROWNUM = 1
|
|
""",
|
|
"STOCK_CODE",
|
|
cancellationToken).ConfigureAwait(false);
|
|
|
|
var domesticCode = domesticStock?.Code;
|
|
var pageRequest = new DomesticPagedQuoteLoadRequest(
|
|
PagedQuoteMarket.Kospi,
|
|
DomesticPagedQuoteList.MarketCapitalization,
|
|
DateOnly.FromDateTime(DateTime.Today));
|
|
|
|
await run.RequiredAsync(
|
|
"s5001",
|
|
"Oracle/BalticDryIndex",
|
|
() => new S5001SceneDataLoader(traced).LoadAsync(
|
|
new S5001BalticDryIndexLoadRequest(), cancellationToken)).ConfigureAwait(false);
|
|
await run.RequiredAsync(
|
|
"s5006",
|
|
"Oracle/domestic",
|
|
() => new S5006DomesticSceneDataLoader(traced).LoadAsync(
|
|
new S5006DomesticSceneDataRequest(
|
|
LegacyDomesticEquityMarket.Kospi,
|
|
RequireSelector(domesticCode, "domestic stock code")),
|
|
cancellationToken)).ConfigureAwait(false);
|
|
await run.RequiredAsync(
|
|
"s5006",
|
|
"MariaDb/NXT",
|
|
() => new S5006NxtSceneDataLoader(traced).LoadAsync(
|
|
LegacyDomesticEquityMarket.NxtKospi,
|
|
RequireSelector(nxtStockCode, "NXT stock code"),
|
|
cancellationToken)).ConfigureAwait(false);
|
|
await run.RequiredAsync(
|
|
"s5011",
|
|
"Oracle/face-value",
|
|
() => new S5011SceneDataLoader(traced).LoadAsync(
|
|
new S5011SceneDataRequest(
|
|
LegacyDomesticEquityMarket.Kospi,
|
|
S5011DetailBranch.FaceValue,
|
|
RequireSelector(domesticCode, "domestic stock code")),
|
|
cancellationToken)).ConfigureAwait(false);
|
|
await run.RequiredAsync(
|
|
"s5016",
|
|
"Oracle/US-indices",
|
|
() => new S5016SceneDataLoader(traced).LoadAsync(
|
|
S5016PanelTarget.UnitedStatesIndices,
|
|
cancellationToken)).ConfigureAwait(false);
|
|
await run.RequiredAsync(
|
|
"s50160",
|
|
"Oracle/cotton",
|
|
() => new S50160SceneDataLoader(traced).LoadAsync(
|
|
S50160PanelTarget.Cotton,
|
|
cancellationToken)).ConfigureAwait(false);
|
|
await run.RequiredAsync(
|
|
"s5023",
|
|
"Oracle/Kospi",
|
|
() => new S5023SceneDataLoader(traced).LoadAsync(
|
|
new S5023SceneLoadRequest(LegacyDomesticMarket.Kospi),
|
|
cancellationToken)).ConfigureAwait(false);
|
|
await run.RequiredAsync(
|
|
"s5024",
|
|
"Oracle/Kospi",
|
|
() => new S5024SceneDataLoader(traced).LoadAsync(
|
|
new S5024SceneLoadRequest(LegacyDomesticMarket.Kospi),
|
|
cancellationToken)).ConfigureAwait(false);
|
|
|
|
var manualDirectory = Environment.GetEnvironmentVariable(
|
|
S5025TrustedManualFileDataSource.DirectoryEnvironmentVariable);
|
|
var manualConfigured = !string.IsNullOrWhiteSpace(manualDirectory);
|
|
if (manualConfigured)
|
|
{
|
|
await run.RequiredAsync(
|
|
"s5025",
|
|
"trusted-external/manual-individual",
|
|
() => new S5025SceneDataLoader(
|
|
S5025TrustedManualFileDataSource.CreateFromEnvironment()).LoadAsync(
|
|
new S5025ManualSceneLoadRequest(S5025ManualAudience.Individual),
|
|
cancellationToken)).ConfigureAwait(false);
|
|
await run.RequiredAsync(
|
|
"s5025",
|
|
"trusted-external/manual-foreign",
|
|
() => new S5025SceneDataLoader(
|
|
S5025TrustedManualFileDataSource.CreateFromEnvironment()).LoadAsync(
|
|
new S5025ManualSceneLoadRequest(S5025ManualAudience.Foreign),
|
|
cancellationToken)).ConfigureAwait(false);
|
|
await run.RequiredAsync(
|
|
"s5025",
|
|
"trusted-external/manual-institution",
|
|
() => new S5025SceneDataLoader(
|
|
S5025TrustedManualFileDataSource.CreateFromEnvironment()).LoadAsync(
|
|
new S5025ManualSceneLoadRequest(S5025ManualAudience.Institution),
|
|
cancellationToken)).ConfigureAwait(false);
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine(
|
|
"SCENE_SMOKE:s5025:PREREQUISITE - trusted external manual-data directory is not configured");
|
|
}
|
|
|
|
await run.RequiredAsync(
|
|
"s5026",
|
|
"Oracle/five-day-candle-pair",
|
|
() => new S5026SceneDataLoader(traced).LoadAsync(
|
|
new ComparisonPairSceneLoadRequest(
|
|
ComparisonGraphPeriod.FiveDays,
|
|
new ComparisonStockSelection(
|
|
ComparisonEquityMarket.Kospi,
|
|
RequireComparisonSelector(comparisonNames, 0)),
|
|
new ComparisonStockSelection(
|
|
ComparisonEquityMarket.Kospi,
|
|
RequireComparisonSelector(comparisonNames, 1))),
|
|
cancellationToken)).ConfigureAwait(false);
|
|
await run.RequiredAsync(
|
|
"s5029",
|
|
"Oracle/five-day-yield-pair",
|
|
() => new S5029SceneDataLoader(traced).LoadAsync(
|
|
new ComparisonPairSceneLoadRequest(
|
|
ComparisonGraphPeriod.FiveDays,
|
|
new ComparisonStockSelection(
|
|
ComparisonEquityMarket.Kospi,
|
|
RequireComparisonSelector(comparisonNames, 0)),
|
|
new ComparisonStockSelection(
|
|
ComparisonEquityMarket.Kospi,
|
|
RequireComparisonSelector(comparisonNames, 1))),
|
|
cancellationToken)).ConfigureAwait(false);
|
|
await run.RequiredAsync(
|
|
"s5032",
|
|
"Oracle/index-and-futures",
|
|
() => new S5032SceneDataLoader(traced).LoadAsync(
|
|
new S5032IndexAndFuturesLoadRequest(LegacyMarketQuoteTarget.Kospi),
|
|
cancellationToken)).ConfigureAwait(false);
|
|
await run.RequiredAsync(
|
|
"s5037",
|
|
"Oracle/dealer-quote",
|
|
() => new S5037SceneDataLoader(traced).LoadAsync(
|
|
new S5037SceneLoadRequest(
|
|
LegacyDomesticMarket.Kospi,
|
|
RequireSelector(dealerStockName, "dealer stock name")),
|
|
cancellationToken)).ConfigureAwait(false);
|
|
await run.RequiredAsync(
|
|
"s5074",
|
|
"Oracle/paged-5",
|
|
() => new S5074SceneDataLoader(traced).LoadAsync(
|
|
pageRequest,
|
|
cancellationToken)).ConfigureAwait(false);
|
|
await run.RequiredAsync(
|
|
"s5076",
|
|
"Oracle/manual-pie",
|
|
() => new S5076SceneDataLoader(traced).LoadAsync(
|
|
RequireSelector(pieName, "INPUT_PIE stock name"),
|
|
cancellationToken)).ConfigureAwait(false);
|
|
await run.RequiredAsync(
|
|
"s5077",
|
|
"Oracle/paged-6",
|
|
() => new S5077SceneDataLoader(traced).LoadAsync(
|
|
pageRequest,
|
|
cancellationToken)).ConfigureAwait(false);
|
|
await run.RequiredAsync(
|
|
"s5078",
|
|
"Oracle/Kospi-sectors",
|
|
() => new S5078SceneDataLoader(traced).LoadAsync(
|
|
new S5078SceneDataRequest(SectorIndexMarket.Kospi),
|
|
cancellationToken)).ConfigureAwait(false);
|
|
await run.RequiredAsync(
|
|
"s5079",
|
|
"Oracle/manual-growth",
|
|
() => new S5079SceneDataLoader(traced).LoadAsync(
|
|
new S5079SceneDataRequest(RequireSelector(growthName, "INPUT_GROW stock name")),
|
|
cancellationToken)).ConfigureAwait(false);
|
|
await run.RequiredAsync(
|
|
"s5080",
|
|
"Oracle/manual-sales",
|
|
() => new S5080SceneDataLoader(traced).LoadAsync(
|
|
new S5080SceneDataRequest(RequireSelector(salesName, "INPUT_SELL stock name")),
|
|
cancellationToken)).ConfigureAwait(false);
|
|
await run.RequiredAsync(
|
|
"s5081",
|
|
"Oracle/manual-profit",
|
|
() => new S5081SceneDataLoader(traced).LoadAsync(
|
|
RequireSelector(profitName, "INPUT_PROFIT stock name"),
|
|
cancellationToken)).ConfigureAwait(false);
|
|
await run.RequiredAsync(
|
|
"s5082",
|
|
"Oracle/grid",
|
|
() => new S5082SceneDataLoader(traced).LoadAsync(cancellationToken)).ConfigureAwait(false);
|
|
await run.RequiredAsync(
|
|
"s5083",
|
|
"Oracle/investor-flow",
|
|
() => new S5083SceneDataLoader(traced).LoadAsync(
|
|
new S5083SceneDataRequest(InvestorFlowParticipant.Individual),
|
|
cancellationToken)).ConfigureAwait(false);
|
|
await run.RequiredAsync(
|
|
"s5084",
|
|
"Oracle/index-flow",
|
|
() => new S5084SceneDataLoader(traced).LoadAsync(
|
|
new S5084SceneDataRequest(InvestorFlowIndex.Kospi),
|
|
cancellationToken)).ConfigureAwait(false);
|
|
await run.RequiredAsync(
|
|
"s5085",
|
|
"Oracle/program-trading",
|
|
() => new S5085SceneDataLoader(traced).LoadAsync(cancellationToken)).ConfigureAwait(false);
|
|
await run.RequiredAsync(
|
|
"s5086",
|
|
"Oracle/five-day-FX-yield",
|
|
() => new S5086SceneDataLoader(traced).LoadAsync(
|
|
new YieldSceneLoadRequest(
|
|
YieldGraphPeriod.FiveDays,
|
|
new YieldGraphInstrument(YieldGraphInstrumentKind.WonDollar)),
|
|
cancellationToken)).ConfigureAwait(false);
|
|
await run.RequiredAsync(
|
|
"s50860",
|
|
"Oracle/five-day-quoted-FX-yield",
|
|
() => new S50860SceneDataLoader(traced).LoadAsync(
|
|
new YieldSceneLoadRequest(
|
|
YieldGraphPeriod.FiveDays,
|
|
new YieldGraphInstrument(YieldGraphInstrumentKind.WonDollar)),
|
|
cancellationToken)).ConfigureAwait(false);
|
|
await run.RequiredAsync(
|
|
"s5087",
|
|
"Oracle/five-day-comparison",
|
|
() => new S5087SceneDataLoader(traced).LoadAsync(
|
|
new ComparisonPairSceneLoadRequest(
|
|
ComparisonGraphPeriod.FiveDays,
|
|
new ComparisonStockSelection(
|
|
ComparisonEquityMarket.Kospi,
|
|
RequireComparisonSelector(comparisonNames, 0)),
|
|
new ComparisonStockSelection(
|
|
ComparisonEquityMarket.Kospi,
|
|
RequireComparisonSelector(comparisonNames, 1))),
|
|
cancellationToken)).ConfigureAwait(false);
|
|
await run.RequiredAsync(
|
|
"s5088",
|
|
"Oracle/paged-12",
|
|
() => new S5088SceneDataLoader(traced).LoadAsync(
|
|
pageRequest,
|
|
cancellationToken)).ConfigureAwait(false);
|
|
await run.RequiredAsync(
|
|
"s6001",
|
|
"Oracle/DowJones",
|
|
() => new S6001SceneDataLoader(traced).LoadAsync(
|
|
S6001QuoteTarget.DowJones,
|
|
cancellationToken)).ConfigureAwait(false);
|
|
await run.RequiredAsync(
|
|
"s6067",
|
|
"Oracle/institution-grid",
|
|
() => new S6067SceneDataLoader(traced).LoadAsync(cancellationToken)).ConfigureAwait(false);
|
|
await run.RequiredAsync(
|
|
"s8001",
|
|
"Oracle/Kospi-sector-map",
|
|
() => new S8001SceneDataLoader(traced).LoadAsync(
|
|
new S8001SceneLoadRequest(S8001Market.Kospi),
|
|
cancellationToken)).ConfigureAwait(false);
|
|
await run.RequiredAsync(
|
|
"s8003",
|
|
"Oracle/order-book",
|
|
() => new S8003SceneDataLoader(traced).LoadAsync(
|
|
new S8003SceneLoadRequest(
|
|
LegacyDomesticMarket.Kospi,
|
|
RequireSelector(orderBookStockCode, "order-book stock code")),
|
|
cancellationToken)).ConfigureAwait(false);
|
|
await run.RequiredAsync(
|
|
"s8010",
|
|
"Oracle/Kospi-index-candle",
|
|
() => new S8010SceneDataLoader(traced).LoadAsync(
|
|
new S8010SceneDataRequest(
|
|
CandleCutAlias.Cut8061,
|
|
CandleMarketTarget.KospiIndex,
|
|
CandleChartMode.Price,
|
|
InstrumentName: null,
|
|
ShowMovingAverage5: false,
|
|
ShowMovingAverage20: false),
|
|
cancellationToken)).ConfigureAwait(false);
|
|
await run.RequiredAsync(
|
|
"s8018",
|
|
"Oracle/current-market-pair",
|
|
() => new S8018SceneDataLoader(traced).LoadAsync(
|
|
new S8018MarketPairLoadRequest(
|
|
S8018MarketDataMode.Current,
|
|
LegacyMarketQuoteTarget.Kospi,
|
|
LegacyMarketQuoteTarget.Kosdaq),
|
|
cancellationToken)).ConfigureAwait(false);
|
|
await run.RequiredAsync(
|
|
"s8067",
|
|
"Oracle/world-map",
|
|
() => new S8067SceneDataLoader(traced).LoadAsync(cancellationToken)).ConfigureAwait(false);
|
|
|
|
await run.DiagnosticAsync(
|
|
"s8086",
|
|
"Oracle/unreachable-no-cut-alias",
|
|
() => new S8086DiagnosticSceneDataLoader(traced).LoadAsync(
|
|
S8086DiagnosticTarget.Gold,
|
|
cancellationToken)).ConfigureAwait(false);
|
|
|
|
run.Complete(manualConfigured);
|
|
}
|
|
|
|
private static async Task<string?> SelectOneAsync(
|
|
IDataQueryExecutor executor,
|
|
string tableName,
|
|
string sql,
|
|
string column,
|
|
CancellationToken cancellationToken) =>
|
|
await SelectOneAsync(
|
|
executor,
|
|
DataSourceKind.Oracle,
|
|
tableName,
|
|
sql,
|
|
column,
|
|
cancellationToken).ConfigureAwait(false);
|
|
|
|
private static async Task<string?> SelectOneAsync(
|
|
IDataQueryExecutor executor,
|
|
DataSourceKind source,
|
|
string tableName,
|
|
string sql,
|
|
string column,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
try
|
|
{
|
|
var table = await executor.ExecuteAsync(
|
|
source,
|
|
tableName,
|
|
new DataQuerySpec(sql),
|
|
cancellationToken).ConfigureAwait(false);
|
|
var value = table.Rows.Count == 1 && table.Columns.Contains(column)
|
|
? Convert.ToString(table.Rows[0][column], CultureInfo.InvariantCulture)?.Trim()
|
|
: null;
|
|
Console.WriteLine(
|
|
string.IsNullOrEmpty(value)
|
|
? $"DB_SMOKE_SELECTOR:{tableName}:NO_ELIGIBLE_ROW"
|
|
: $"DB_SMOKE_SELECTOR:{tableName}:PASS");
|
|
return string.IsNullOrEmpty(value) ? null : value;
|
|
}
|
|
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
|
{
|
|
throw;
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
Console.WriteLine($"DB_SMOKE_SELECTOR:{tableName}:FAIL:{SafeFailure(exception)}");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private static async Task<StockSelector?> SelectStockAsync(
|
|
IDataQueryExecutor executor,
|
|
string tableName,
|
|
string sql,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
try
|
|
{
|
|
var table = await executor.ExecuteAsync(
|
|
DataSourceKind.Oracle,
|
|
tableName,
|
|
new DataQuerySpec(sql),
|
|
cancellationToken).ConfigureAwait(false);
|
|
if (table.Rows.Count != 1 ||
|
|
!table.Columns.Contains("STOCK_CODE") ||
|
|
!table.Columns.Contains("STOCK_NAME"))
|
|
{
|
|
Console.WriteLine($"DB_SMOKE_SELECTOR:{tableName}:NO_ELIGIBLE_ROW");
|
|
return null;
|
|
}
|
|
|
|
var code = Convert.ToString(table.Rows[0]["STOCK_CODE"], CultureInfo.InvariantCulture)?.Trim();
|
|
var name = Convert.ToString(table.Rows[0]["STOCK_NAME"], CultureInfo.InvariantCulture)?.Trim();
|
|
if (string.IsNullOrEmpty(code) || string.IsNullOrEmpty(name))
|
|
{
|
|
Console.WriteLine($"DB_SMOKE_SELECTOR:{tableName}:NO_ELIGIBLE_ROW");
|
|
return null;
|
|
}
|
|
|
|
Console.WriteLine($"DB_SMOKE_SELECTOR:{tableName}:PASS");
|
|
return new StockSelector(code, name);
|
|
}
|
|
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
|
{
|
|
throw;
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
Console.WriteLine($"DB_SMOKE_SELECTOR:{tableName}:FAIL:{SafeFailure(exception)}");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private static async Task<IReadOnlyList<string>> SelectManyAsync(
|
|
IDataQueryExecutor executor,
|
|
string tableName,
|
|
string sql,
|
|
string column,
|
|
int maximumRows,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
try
|
|
{
|
|
var table = await executor.ExecuteAsync(
|
|
DataSourceKind.Oracle,
|
|
tableName,
|
|
new DataQuerySpec(sql),
|
|
cancellationToken).ConfigureAwait(false);
|
|
if (!table.Columns.Contains(column) || table.Rows.Count > maximumRows)
|
|
{
|
|
Console.WriteLine($"DB_SMOKE_SELECTOR:{tableName}:INVALID_RESULT");
|
|
return [];
|
|
}
|
|
|
|
var values = table.Rows.Cast<DataRow>()
|
|
.Select(row => Convert.ToString(row[column], CultureInfo.InvariantCulture)?.Trim())
|
|
.Where(value => !string.IsNullOrEmpty(value))
|
|
.Cast<string>()
|
|
.Distinct(StringComparer.Ordinal)
|
|
.ToArray();
|
|
Console.WriteLine(
|
|
values.Length == 0
|
|
? $"DB_SMOKE_SELECTOR:{tableName}:NO_ELIGIBLE_ROW"
|
|
: $"DB_SMOKE_SELECTOR:{tableName}:PASS:{values.Length.ToString(CultureInfo.InvariantCulture)}");
|
|
return values;
|
|
}
|
|
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
|
{
|
|
throw;
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
Console.WriteLine($"DB_SMOKE_SELECTOR:{tableName}:FAIL:{SafeFailure(exception)}");
|
|
return [];
|
|
}
|
|
}
|
|
|
|
private static string RequireSelector(string? value, string selector)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(value))
|
|
{
|
|
throw new LegacySceneDataException(
|
|
$"The {selector} selector returned no eligible database row.");
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
private static string RequireComparisonSelector(
|
|
IReadOnlyList<string> values,
|
|
int index)
|
|
{
|
|
if (values.Count != 2 || index is < 0 or > 1)
|
|
{
|
|
return RequireSelector(null, "comparison stock pair");
|
|
}
|
|
|
|
return values[index];
|
|
}
|
|
|
|
private static string SafeFailure(Exception exception) => exception switch
|
|
{
|
|
LegacySceneDataException => "DATA_UNAVAILABLE_OR_INVALID",
|
|
DatabaseOperationException operation when operation.ProviderErrorCode.HasValue =>
|
|
"DATABASE_QUERY_FAILED_CODE_" +
|
|
operation.ProviderErrorCode.Value.ToString(CultureInfo.InvariantCulture),
|
|
DatabaseInfrastructureException => "DATABASE_QUERY_FAILED",
|
|
S5025TrustedManualDataException => "TRUSTED_MANUAL_DATA_UNAVAILABLE",
|
|
_ => exception.GetType().Name.ToUpperInvariant()
|
|
};
|
|
|
|
private sealed record StockSelector(string Code, string Name);
|
|
|
|
private sealed class SceneSmokeRun
|
|
{
|
|
private readonly HashSet<string> _covered = new(StringComparer.Ordinal);
|
|
private readonly List<string> _requiredFailures = [];
|
|
|
|
public async Task RequiredAsync<TData>(
|
|
string builderKey,
|
|
string caseName,
|
|
Func<Task<TData>> loadAsync)
|
|
where TData : ILegacySceneData =>
|
|
await VerifyAsync(builderKey, caseName, loadAsync, required: true).ConfigureAwait(false);
|
|
|
|
public async Task DiagnosticAsync<TData>(
|
|
string builderKey,
|
|
string caseName,
|
|
Func<Task<TData>> loadAsync)
|
|
where TData : ILegacySceneData =>
|
|
await VerifyAsync(builderKey, caseName, loadAsync, required: false).ConfigureAwait(false);
|
|
|
|
public void Complete(bool manualConfigured)
|
|
{
|
|
var expected = LegacySceneMutationBuilderRegistry.Default.Registrations
|
|
.Select(registration => registration.BuilderKey)
|
|
.Where(key => !string.Equals(key, "s8086", StringComparison.Ordinal))
|
|
.Where(key => manualConfigured || !string.Equals(key, "s5025", StringComparison.Ordinal))
|
|
.ToHashSet(StringComparer.Ordinal);
|
|
var missing = expected.Except(_covered, StringComparer.Ordinal)
|
|
.OrderBy(key => key, StringComparer.Ordinal)
|
|
.ToArray();
|
|
var requiredCovered = expected.Count(_covered.Contains);
|
|
var diagnosticStatus = _covered.Contains("s8086")
|
|
? "pass-unreachable-no-cut-alias"
|
|
: "failed-unreachable-no-cut-alias";
|
|
|
|
Console.WriteLine(
|
|
$"SCENE_SMOKE_SUMMARY: requiredCovered={requiredCovered.ToString(CultureInfo.InvariantCulture)} " +
|
|
$"required={expected.Count.ToString(CultureInfo.InvariantCulture)} " +
|
|
$"requiredFailures={_requiredFailures.Count.ToString(CultureInfo.InvariantCulture)} " +
|
|
$"diagnostic={diagnosticStatus} " +
|
|
$"manualS5025={(manualConfigured ? "configured" : "prerequisite")}");
|
|
if (missing.Length > 0)
|
|
{
|
|
Console.WriteLine("SCENE_SMOKE_MISSING: " + string.Join(",", missing));
|
|
}
|
|
|
|
if (_requiredFailures.Count > 0 || missing.Length > 0)
|
|
{
|
|
throw new DatabaseConfigurationException(
|
|
"One or more required real-data scene loader cases did not produce validated mutations.");
|
|
}
|
|
}
|
|
|
|
private async Task VerifyAsync<TData>(
|
|
string builderKey,
|
|
string caseName,
|
|
Func<Task<TData>> loadAsync,
|
|
bool required)
|
|
where TData : ILegacySceneData
|
|
{
|
|
Console.WriteLine($"SCENE_SMOKE:{builderKey}:{caseName}:START");
|
|
try
|
|
{
|
|
var data = await loadAsync().ConfigureAwait(false);
|
|
var mutations = LegacySceneMutationBuilderRegistry.Default.Build(builderKey, data);
|
|
if (mutations.Count == 0)
|
|
{
|
|
throw new LegacySceneDataException(
|
|
"The scene mutation builder produced no mutations.");
|
|
}
|
|
|
|
_covered.Add(builderKey);
|
|
Console.WriteLine(
|
|
$"SCENE_SMOKE:{builderKey}:{caseName}:PASS:" +
|
|
mutations.Count.ToString(CultureInfo.InvariantCulture));
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
throw;
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
var failure = SafeFailure(exception);
|
|
Console.WriteLine($"SCENE_SMOKE:{builderKey}:{caseName}:FAIL:{failure}");
|
|
if (required)
|
|
{
|
|
_requiredFailures.Add(builderKey + "/" + caseName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private sealed class QueryTracingDataQueryExecutor : IDataQueryExecutor
|
|
{
|
|
private readonly IDataQueryExecutor _inner;
|
|
private int _sequence;
|
|
|
|
public QueryTracingDataQueryExecutor(IDataQueryExecutor inner)
|
|
{
|
|
_inner = inner ?? throw new ArgumentNullException(nameof(inner));
|
|
}
|
|
|
|
public DataTable Execute(DataSourceKind source, string tableName, string query)
|
|
{
|
|
var sequence = Start(source, tableName);
|
|
try
|
|
{
|
|
var result = _inner.Execute(source, tableName, query);
|
|
End(sequence, result);
|
|
return result;
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
Fail(sequence, exception);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public async Task<DataTable> ExecuteAsync(
|
|
DataSourceKind source,
|
|
string tableName,
|
|
string query,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var sequence = Start(source, tableName);
|
|
try
|
|
{
|
|
var result = await _inner.ExecuteAsync(
|
|
source,
|
|
tableName,
|
|
query,
|
|
cancellationToken).ConfigureAwait(false);
|
|
End(sequence, result);
|
|
return result;
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
Fail(sequence, exception);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public async Task<DataTable> ExecuteAsync(
|
|
DataSourceKind source,
|
|
string tableName,
|
|
DataQuerySpec query,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var sequence = Start(source, tableName);
|
|
try
|
|
{
|
|
var result = await _inner.ExecuteAsync(
|
|
source,
|
|
tableName,
|
|
query,
|
|
cancellationToken).ConfigureAwait(false);
|
|
End(sequence, result);
|
|
return result;
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
Fail(sequence, exception);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
private int Start(DataSourceKind source, string tableName)
|
|
{
|
|
var sequence = Interlocked.Increment(ref _sequence);
|
|
Console.WriteLine(
|
|
$"DB_SMOKE_QUERY:{sequence.ToString(CultureInfo.InvariantCulture)}:" +
|
|
$"START:{source}:{tableName}");
|
|
return sequence;
|
|
}
|
|
|
|
private static void End(int sequence, DataTable result) =>
|
|
Console.WriteLine(
|
|
$"DB_SMOKE_QUERY:{sequence.ToString(CultureInfo.InvariantCulture)}:" +
|
|
$"PASS:rows={result.Rows.Count.ToString(CultureInfo.InvariantCulture)}");
|
|
|
|
private static void Fail(int sequence, Exception exception) =>
|
|
Console.WriteLine(
|
|
$"DB_SMOKE_QUERY:{sequence.ToString(CultureInfo.InvariantCulture)}:" +
|
|
$"FAIL:{SafeFailure(exception)}");
|
|
}
|
|
}
|