@memberjunction/sql-dialect is an abstract SQL dialect layer that enables database-agnostic SQL generation across MemberJunction. It encapsulates every platform-specific SQL syntax pattern -- identifier quoting, pagination, data types, DDL generation, full-text search, and more -- into a single, testable abstraction with zero database driver dependencies.
This package is used by CodeGen, data providers, and SQL converters throughout the MemberJunction monorepo. When code needs to emit SQL that works on both SQL Server and PostgreSQL, it programs against the SQLDialect abstract class and lets the concrete dialect handle platform differences.
SQLDialect defines approximately 30 abstract methods spanning identifier quoting, pagination, literal expressions, INSERT/UPDATE return patterns, DDL generation, full-text search, data type mapping, and schema introspection. Each concrete dialect implements every method with platform-native SQL.
Catalog query templates for discovering tables, columns, constraints, foreign keys, and indexes.
TriggerOptions
Configuration for trigger DDL generation (schema, table, timing, events, body, function name, FOR EACH ROW/STATEMENT).
IndexOptions
Configuration for index DDL generation (columns, uniqueness, method, partial WHERE, INCLUDE columns).
DataTypeMap
Maps source database types to target platform types.
MappedType
Describes a mapped type: typeName, supportsLength, supportsPrecisionScale, defaultLength.
DatabasePlatform
Union type: 'sqlserver' | 'postgresql'
Key Methods
Identifier Quoting
Method
Description
SQL Server
PostgreSQL
QuoteIdentifier(name)
Wraps a single identifier
[name]
"name"
QuoteSchema(schema, object)
Schema-qualified reference
[schema].[object]
schema."object"
Pagination
Method
Description
SQL Server
PostgreSQL
LimitClause(limit, offset?)
Returns { prefix, suffix }
Without offset: prefix: 'TOP 10'. With offset: suffix: 'OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY'
suffix: 'LIMIT 10 OFFSET 20'
Literals and Expressions
Method
Description
SQL Server
PostgreSQL
BooleanLiteral(value)
Platform boolean
1 / 0
true / false
CurrentTimestampUTC()
Current UTC time
GETUTCDATE()
(NOW() AT TIME ZONE 'UTC')
NewUUID()
Generate UUID
NEWID()
gen_random_uuid()
CastToText(expr)
Cast to text type
CAST(expr AS NVARCHAR(MAX))
CAST(expr AS TEXT)
CastToUUID(expr)
Cast to UUID type
CAST(expr AS UNIQUEIDENTIFIER)
CAST(expr AS UUID)
Coalesce(expr, fallback)
Null coalescing (concrete)
COALESCE(expr, fallback)
COALESCE(expr, fallback)
IsNull(expr, fallback)
Alias for Coalesce
COALESCE(expr, fallback)
COALESCE(expr, fallback)
IIF(condition, trueVal, falseVal)
Conditional expression
IIF(cond, t, f)
CASE WHEN cond THEN t ELSE f END
INSERT/UPDATE Return Patterns
Method
Description
SQL Server
PostgreSQL
ReturnInsertedClause(columns?)
Get inserted values back
OUTPUT INSERTED.* or OUTPUT INSERTED.[col]
RETURNING * or RETURNING "col"
AutoIncrementPKExpression()
Auto-increment DDL
IDENTITY(1,1)
GENERATED ALWAYS AS IDENTITY
UUIDPKDefault()
Default UUID PK expression
NEWSEQUENTIALID()
gen_random_uuid()
ScopeIdentityExpression()
Last inserted identity
SCOPE_IDENTITY()
lastval()
RowCountExpression()
Rows affected
@@ROWCOUNT
ROW_COUNT (via GET DIAGNOSTICS)
DDL Generation
Method
Description
TriggerDDL(options: TriggerOptions)
Full trigger creation DDL. SQL Server emits CREATE TRIGGER ... AS BEGIN ... END. PostgreSQL emits a companion CREATE OR REPLACE FUNCTION plus CREATE TRIGGER ... EXECUTE FUNCTION.
IndexDDL(options: IndexOptions)
Index creation DDL. PostgreSQL supports USING method, partial WHERE, and IF NOT EXISTS. SQL Server supports INCLUDE columns.
ExistenceCheckSQL(objectType, schema, name)
Check if a database object exists. SQL Server uses OBJECT_ID(). PostgreSQL uses pg_catalog queries. Supports TABLE, VIEW, FUNCTION, PROCEDURE, TRIGGER.
CreateOrReplaceSupported(objectType)
Whether CREATE OR REPLACE is available. SQL Server: always false. PostgreSQL: true for FUNCTION, VIEW, PROCEDURE.
Returns a SchemaIntrospectionSQL object with platform-specific catalog queries for listing tables, columns, constraints, foreign keys, indexes, and checking object existence.
Data Type Mapping
Method
Description
get TypeMap(): DataTypeMap
Returns the dialect-specific type mapper instance.
Convenience wrapper that calls TypeMap.MapTypeToString(). Returns a formatted type string like VARCHAR(255) or NUMERIC(10,2).
DataTypeMap
The DataTypeMap interface defines how data types are translated between database platforms:
interfaceMappedType { typeName: string; // Target type name (e.g., "UUID", "BOOLEAN") supportsLength: boolean; // Whether the type accepts a length parameter supportsPrecisionScale: boolean; // Whether the type accepts precision/scale defaultLength?: number; // Default length when applicable }
SQLServerDataTypeMap is an identity mapper (SQL Server types map to themselves). PostgreSQLDataTypeMap maps SQL Server types to their PostgreSQL equivalents.
// Trigger DDL consttriggerSQL = dialect.TriggerDDL({ schema:'__mj', tableName:'User', triggerName:'trgUpdateUser', timing:'BEFORE', events: ['UPDATE'], body:'NEW.__mj_UpdatedAt = NOW();', functionName:'fn_update_user_timestamp', forEach:'ROW' }); // Generates: // CREATE OR REPLACE FUNCTION __mj."fn_update_user_timestamp"() // RETURNS TRIGGER AS $$ BEGIN ... END; $$ LANGUAGE plpgsql; // DROP TRIGGER IF EXISTS ... ; // CREATE TRIGGER "trgUpdateUser" BEFORE UPDATE ON __mj."User" // FOR EACH ROW EXECUTE FUNCTION __mj."fn_update_user_timestamp"();
// Index DDL constindexSQL = dialect.IndexDDL({ schema:'__mj', tableName:'User', indexName:'idx_user_email', columns: ['Email'], unique:true, method:'btree' }); // 'CREATE UNIQUE INDEX IF NOT EXISTS "idx_user_email" // ON __mj."User" USING btree("Email")'
Polymorphic Usage
The key benefit is writing database-agnostic code that works with any dialect:
The query rendering members decide how row caps, paging, composed queries and the RunQuery
template filters behave on the platform; see Query rendering members.
Update the DatabasePlatform type in sqlDialect.ts to include the new platform key.
Export from index.ts:
export { MySQLDialect } from'./mysqlDialect.js';
Add tests in src/__tests__/mysqlDialect.test.ts covering every method. The existing crossDialect.test.ts provides a pattern for testing multiple dialects against the same assertions.
Side-by-Side Dialect Comparison
Feature
SQL Server (SQLServerDialect)
PostgreSQL (PostgreSQLDialect)
Identifier quoting
[name]
"name"
Schema-qualified
[schema].[object]
schema."object"
Boolean literals
1 / 0
true / false
Current UTC time
GETUTCDATE()
(NOW() AT TIME ZONE 'UTC')
New UUID
NEWID()
gen_random_uuid()
UUID PK default
NEWSEQUENTIALID()
gen_random_uuid()
Auto-increment
IDENTITY(1,1)
GENERATED ALWAYS AS IDENTITY
Pagination (no offset)
SELECT TOP 10 ...
... LIMIT 10
Pagination (with offset)
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY
LIMIT 10 OFFSET 20
Return inserted
OUTPUT INSERTED.*
RETURNING *
Scope identity
SCOPE_IDENTITY()
lastval()
Row count
@@ROWCOUNT
ROW_COUNT (GET DIAGNOSTICS)
Concatenation
+
||
Parameters
@p0, @p1, ...
$1, $2, ...
Batch separator
GO
(none)
Conditional
IIF(cond, t, f)
CASE WHEN cond THEN t ELSE f END
Recursive CTE
WITH
WITH RECURSIVE
Procedure call
EXEC [s].[name] @p0
SELECT * FROM s."name"($1)
CREATE OR REPLACE
Not supported
FUNCTION, VIEW, PROCEDURE
Full-text search
CONTAINS([col], term)
col @@ plainto_tsquery(...)
JSON extract
JSON_VALUE(col, 'path')
col->>'path'
String split
STRING_SPLIT(val, delim)
unnest(string_to_array(val, delim))
Cast to text
CAST(x AS NVARCHAR(MAX))
CAST(x AS TEXT)
Cast to UUID
CAST(x AS UNIQUEIDENTIFIER)
CAST(x AS UUID)
Object existence
IF OBJECT_ID(...) IS NOT NULL
SELECT EXISTS (... pg_catalog ...)
Comments/descriptions
sp_addextendedproperty
COMMENT ON ...
Grants
GRANT ... ON [s].[o] TO [r]
GRANT ... ON s."o" TO "r"
Query rendering members
The RunQuery rendering pipeline (SQLParser's lexer and paging-shape analysis, the paging and
composition engines in @memberjunction/generic-database-provider, the template filters in
@memberjunction/core and the parameter processor) reads these members rather than the platform
name, so a new dialect gets correct rendering by declaring them:
Member
SQL Server
PostgreSQL
Used for
LimitClause(limit, offset?)
TOP n / OFFSET … FETCH
LIMIT … OFFSET
Row caps and pages
DefaultPagingOrderBy
(SELECT NULL)
1
Paging a query with no ORDER BY
SelectListPagingOrderBy
1
null
Paging a set operation or SELECT DISTINCT with no ORDER BY
PagingRequiresOrderBy
true
false
Whether a page from a derived table needs an ORDER BY
AllowsOrderByInCTE
false
true
Stripping a composed dependency's ORDER BY
RecursiveCTESyntax()
WITH
WITH RECURSIVE
Composing into a recursive WITH clause
QueryHintKeyword
OPTION
null
Keeping a trailing hint clause last; lifting a dependency's hints
CallerSQLForbiddenFunctions
openrowset, openquery, …
query_to_xml*, ts_stat, pg_read_file, dblink*, …
Refusing calls in caller-supplied SQL that read what a check of its tables cannot see
SupportsEscapeStringLiterals
false
true
Lexing E'…' strings
SupportsDollarQuotedStrings
false
true
Lexing $$…$$ strings
QuoteIdentifier(name)
[name]
"name"
Lexing quoted identifiers; sqlIdentifier
StringLiteralPrefix(text)
N for non-ASCII
none
sqlString, sqlIn, LIKE filters
EscapeLikePattern(text)
[%], [_], [[]
\%, \_, \\
LIKE filters
BooleanLiteral(value)
1 / 0
true / false
sqlBoolean
BooleanParameterValue(value)
1 / 0
true / false
Binding a boolean query parameter
ParserDialect
TransactSQL
PostgresQL
node-sql-parser grammar, where the AST is used
Installation
npminstall@memberjunction/sql-dialect
Or, in a MemberJunction workspace, add the dependency to your package's package.json and run npm install from the repo root.