using System.Data.Common; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; using ZB.MOM.WW.OtOpcUa.Driver.Sql.Contracts; namespace ZB.MOM.WW.OtOpcUa.Driver.Sql; /// /// The provider seam (design §2.2). ADO.NET's base types abstract /// connections, commands, parameters and readers; they do not abstract the three things that differ /// per backend — identifier quoting, the metadata-catalog SQL, and row-limit syntax. /// Those live here, so no dialect assumption leaks into the reader, the poll planner, or the schema browser. /// This interface is the driver's SQL-injection boundary. Every runtime value is /// bound as a and never reaches a command text. Identifiers cannot be /// parameterized in SQL, so the few that must appear as text are emitted through /// . Any code path that builds SQL by concatenating an authored tag field /// without going through is a defect (design §8.1). /// Quoting is the backstop, not the only defence. Design §8.1's catalog gate is /// implemented: resolves every authored table/column against the live /// catalog at Initialize and replaces it with the catalog's own spelling, so an identifier /// reaching on the poll path is a string this driver read back out of /// / / — not /// operator input. An identifier that resolves to nothing rejects its tag (which then publishes /// BadNodeIdUnknown) rather than being quoted into a query against a nonexistent object. /// Public because Driver.Sql.Browser consumes it — the catalog SQL is the browse /// engine, so it is shared rather than duplicated. Implementations own their provider package; /// no provider-specific type appears in this signature ( is the abstract /// ). /// public interface ISqlDialect { /// Which backend this dialect speaks. v1 constructs only. SqlProvider Provider { get; } /// /// The provider's factory singleton, used to create connections/commands/parameters. Deliberately the /// abstract base type so consumers never bind to a concrete provider package. /// DbProviderFactory Factory { get; } /// /// Quotes one identifier part so it can be safely embedded in a command text, escaping the /// dialect's own quote character. Rejects anything that cannot be a real catalog identifier rather /// than emitting it. /// One part only. A multi-part name such as dbo.TagValues must be split by the /// caller and each part quoted separately; passing the dotted form yields a single (nonexistent) /// identifier — safe, but not what the caller meant. /// /// The bare, unquoted identifier — as returned by the catalog, not pre-quoted. /// The quoted identifier, ready to concatenate into a command text. /// The value cannot be a valid catalog identifier. string QuoteIdentifier(string ident); /// Cheapest statement that proves the connection is alive (SELECT 1; Oracle needs FROM DUAL). string LivenessSql { get; } /// /// The fragment that goes immediately after SELECT to limit a statement to one row — /// T-SQL's "TOP 1 ". Empty for a dialect that spells the limit at the end of the statement (see /// ). /// Include the trailing space when non-empty: the planner concatenates /// "SELECT " + SingleRowLimitPrefix + columns with no separator of its own, so an empty fragment /// must leave the statement byte-identical to an unlimited one. /// /// /// Why a prefix/suffix pair rather than one ApplyRowLimit(sql, n) method. The two /// spellings sit at opposite ends of the statement — SQL Server writes /// SELECT TOP 1 … ORDER BY … DESC, while SQLite/Postgres/MySQL write /// SELECT … ORDER BY … DESC LIMIT 1. A single method taking the SELECT list could only serve the /// prefix position; a single method taking the whole statement would move statement assembly out of the /// planner and into every dialect. Two fragments keep the planner the sole author of statement shape /// and let each dialect fill in the end it uses. /// Why "single row" rather than a row count. The only limit v1 emits is the wide-row /// topByTimestamp selector's newest-row pick. Modelling an arbitrary n would be untested /// surface, and FETCH FIRST/ROWNUM dialects need more than a substituted number anyway. /// Widen this to a method when a feature actually needs n > 1. /// string SingleRowLimitPrefix { get; } /// /// The fragment appended to the very end of a statement to limit it to one row — /// " LIMIT 1" for SQLite/Postgres/MySQL. Empty for a dialect that limits after SELECT /// (see ). /// Include the leading space when non-empty, for the same reason the prefix carries a /// trailing one: the planner appends it directly to the finished statement. /// Exactly one of the two fragments is non-empty in every dialect modelled so far, but the planner /// emits both unconditionally — a dialect needing both ends (or neither) is expressible without /// touching the call site. /// string SingleRowLimitSuffix { get; } /// Catalog query listing schemas — the browser's RootAsync level. Takes no parameters. string ListSchemasSql { get; } /// /// Scalar query returning the schema an unqualified object name resolves to for the connecting /// principal — T-SQL's SELECT SCHEMA_NAME(). Takes no parameters. /// /// /// Needed by : a tag authored as TagValues rather than /// dbo.TagValues has to be looked up in some schema, and guessing dbo would be a /// silent lie on any estate that maps its service accounts to their own default schema. Asking the /// server is the only answer that matches how the poll query itself will resolve the name. /// It is a query rather than a name because the answer is per-connection, not per-dialect /// — the same dialect resolves differently for two different logins. /// string DefaultSchemaSql { get; } /// Catalog query listing tables + views in one schema — the browser's schema-expand level. Bind @schema. string ListTablesSql { get; } /// Catalog query listing columns of one table — the browser's table-expand / attributes level. Bind @schema and @table. string ListColumnsSql { get; } /// /// Folds a catalog data-type family name (as INFORMATION_SCHEMA.COLUMNS.DATA_TYPE reports it — /// e.g. nvarchar, never nvarchar(50)) onto a . /// Must never throw: a browse over a table holding one exotic column must still render. /// An unrecognised family falls back to . /// /// The bare SQL type family name; matched case-insensitively. /// The mapped driver data type, or when unrecognised. DriverDataType MapColumnType(string sqlDataType); }