Protected_The database-specific code generation provider, resolved from
MJGlobal.ClassFactory against the configured dbPlatform. Each provider
(SQLServerCodeGenProvider, PostgreSQLCodeGenProvider, …) registers
itself with @RegisterClass(CodeGenDatabaseProvider, '<platform>') —
the canonical DatabasePlatform value as its key — so this
single line picks up the right one without dialect-specific branching.
Subclasses can override via @RegisterClass(... priority) to plug in
customized codegen behavior, same hook every other MJ class uses.
Protected_ProtectedcascadeTracks cascade delete dependencies between entities. Key: Entity ID whose update/delete SP is called by other entities' delete SPs Value: Set of Entity IDs that have CascadeDeletes=true and call this entity's update/delete SP
ProtectedentitiesTracks entities that need their delete stored procedures regenerated due to cascade dependencies
ProtectedentitiesArray of entity names that qualify for forced regeneration based on the whereClause filter
Protectedexistingschema.routine keys (lowercased) present in the database at the start of
this SQL-generation pass. Used to force-log CREATE PROC for routines that
were dropped out-of-band (BigSchemaDemo recreate, a failed prior batch)
while the entity itself is not in newEntityList/modifiedEntityList.
ProtectedfilterFlag indicating whether to filter entities for forced regeneration based on entityWhereClause
ProtectedorderedOrdered list of entity IDs for delete SP regeneration (dependency order)
ProtectedanalyzeAnalyzes cascade delete dependencies without generating SQL. This method only tracks which entities depend on others for cascade operations.
ProtectedapplyApply reason-specific fixups after late-phase view regeneration. Each regen reason may require additional SQL to be executed and logged to the migration output file.
ProtectedbuildBuilds a complete map of cascade delete dependencies by analyzing all entities. This method populates the cascadeDeleteDependencies map without generating SQL.
ProtectedbuildBuilds a combined SQL string of sp_refreshview statements for the given entities.
For a LAYERED entity the outer refresh is guarded on the application-owned BaseView
existing, matching generateCustomBaseViewRefreshAndPermissions. On the first pass
after layering is enabled the outer view cannot exist yet — it selects from the inner view
that same pass creates — so an unguarded refresh here fails the documented setup procedure.
The inner view is refreshed first, before the outer. That ordering is defensive rather than
load-bearing: every entity reaching this method is in the modified/new list (see
getModifiedCustomBaseViewEntities), and logSQLForNewOrModifiedEntity forces a
modified entity's base view DDL into the same migration regardless of whether its text
changed — so the inner view is already dropped and recreated in an earlier step, which
resets its cached column list more thoroughly than a refresh would. The ordering is kept
anyway so this path cannot become wrong if that coupling is ever loosened.
ProtectedbuildProtectedbuildProtectedcheckFetches the current base view definition from SQL Server and compares it against newly generated SQL. If the view body has changed (e.g., a virtual field was added to a joined table), the entity is logged and executed. Unlike adding to modifiedEntityList (which would cause all SPs, permissions, indexes, etc. to be logged too), this only logs the base view SQL itself.
Comparison extracts the SELECT body from both the DB definition (CREATE VIEW ... AS
) and the generated SQL, normalizing whitespace for a fair comparison.true if the base view changed, false otherwise
ProtectedcheckPostgreSQL base-view change detection by COLUMN SET rather than text.
pg_get_viewdef() re-qualifies columns and normalizes whitespace/casing, so the SELECT-body text comparison used on SQL Server never byte-matches on PG and false-flags nearly every view. The base view exposes exactly the entity's fields, so comparing the view's live column set (information_schema.columns) against the entity's expected field set is deterministic, immune to that reformatting, and still catches the cases that matter:
NOT caught (by design — a tradeoff vs. SQL Server's text comparison): a view-BODY change that
leaves the exposed column NAMES identical — e.g. a RelatedEntityNameFieldMap re-pointed to a
different source column under the same output alias, or a codegen view-template change that
rewrites the SELECT without renaming columns. Those alter the SELECT text but not the column set,
so they must be forced through the metadata-driven modifiedEntityList (or an explicit
EntitiesRequiringViewRegen entry), not this column-set check.
true if the view's column set differs from the entity's fields (or the view is missing)
ProtectedcreateProtectedcreateProtectedcreateProtecteddetectDetects self-referential foreign keys in an entity (e.g., ParentTaskID pointing back to Task table) Returns array of field info objects representing recursive relationships
ProtectedemitEmits sp_refreshview statements into the migration log file for modified custom base view entities (BaseViewGenerated === false). This ensures that when the migration runs on other environments, custom base views affected by schema changes get refreshed automatically. Only entities in the modified or new entity lists are included.
Generates the entity's per-phase SQL pieces (view, CRUD functions, view permissions) and dispatches them to the provider's phased executor. Lives here because it needs access to the internal generators; the provider doesn't own context building for the view.
ProtectedextractExtracts the SELECT body from a CREATE VIEW statement, normalizing whitespace for comparison. Handles both DB-stored definitions (CREATE VIEW ... AS SELECT ...) and generated SQL (which includes DROP VIEW, comment headers, GO, and permissions after the view body).
ProtectedforceProtectedformatFormats a default value for use in SQL, handling special cases like SQL functions
The default value from the database metadata
Whether the field type typically needs quotes
Properly formatted default value for SQL
ProtectedgenerateGenerates all inline Table Value Functions for an entity's recursive foreign keys Returns empty string if no recursive FKs exist
This function will handle the process of creating all of the generated objects like base view, stored procedures, etc. for all entities in the entities array. It will generate the SQL for each entity and then execute it.
OptionalbatchSize?: numberOptionalenableSQLLoggingForNewOrModifiedEntities?: booleanOptionalwillRegenerate?: Set<string>Optional UNION of will-regenerate keys across ALL batches in the same codegen run. When provided, takes precedence over the per-batch set.
OptionalenableSQLLoggingForNewOrModifiedEntities?: booleanOptionalwillRegenerate?: Set<string>Names of other entities regenerating in this run — passed to the PG phased executor's willRegenerate so stale captured dependents that will be rebuilt anyway aren't restored.
ProtectedgenerateSame as generateBaseView, but returns the view DDL and the GRANT/permissions
DDL as separate strings rather than concatenating. Used by phased per-entity
execution paths (e.g. PG's phased executor) where the view must run under
42P16 fallback recovery but the permissions must run afterwards in a later
phase — concatenating them would prevent the executor from detecting where
a failure actually occurred.
ProtectedgenerateProtectedgenerateProtectedgenerateEmits the refresh + permission statements for an entity whose BaseView CodeGen does NOT
write — fully custom, or the application-owned outer half of a layered entity.
The refresh runs before the grants so SQL Server picks up schema changes (new columns from migrations) before permissions are applied; developers no longer add it by hand.
Two things are specific to LAYERED entities:
g.* from the generated one, and a view
caches its column list. Refreshing the outer against a stale inner re-caches the OLD
columns, so a newly added column stays missing — indistinguishable from never having been
added at all, which is the exact failure layering exists to eliminate.GRANT both fail — and the step they fail on is the documented setup procedure, so
there would be no way to adopt the feature at all. Guarding makes the bootstrap pass skip
them and leaves every later pass identical to the unguarded form.ProtectedgenerateProtectedgenerateProtectedgenerateBubble lat/lng from EmbeddedRecord peers as __mj_Latitude_{FK}.
Geocode lives on the Address (or other source); the parent only displays it.
ProtectedgenerateProtectedgenerateGenerates the SELECT clause additions for hierarchy metadata fields from TVFs
Projects: Root
ProtectedgenerateGenerates JOIN clauses to inline Table Value Functions for hierarchy metadata calculation
Generates the SQL for creating indexes for the foreign keys in the entity. Delegates to the database provider for platform-specific index DDL.
Generates the composite index covering the entity's SOFT primary key. Empty for every
entity with a real PRIMARY KEY constraint, which is nearly all of them.
Kept separate from generateIndexesForForeignKeys and gated by its own setting: a soft-PK index serves MJ's own per-record existence check on the create path, which is a different concern from indexing foreign keys for joins and filters, and an operator's opinion about one is not an opinion about the other.
ProtectedgenerateGenerate SELECT aliases for native lat/lng fields → __mj_Latitude / __mj_Longitude. Virtual fields are excluded — see hasNativeGeoFields for the rationale.
ProtectedgenerateGenerates SELECT field expressions for IS-A parent entity columns. Walks the ParentID chain upward, joining to each parent's base table, and includes non-PK, non-timestamp, non-virtual fields from each parent table. Returns a string starting with ',\n' if there are parent fields, or '' if none.
ProtectedgenerateGenerates INNER JOIN clauses for IS-A parent entity base tables. Chains joins from child -> parent -> grandparent using PK-to-PK conditions. Each parent is joined via its base table (not view) to avoid view dependency ordering issues.
ProtectedgenerateGenerate SELECT aliases from the RecordGeoCode LEFT JOIN → __mj_Latitude / __mj_Longitude.
ProtectedgenerateProtectedgenerateGenerates the SELECT clause additions for root and hierarchy fields from TVFs
ProtectedgenerateGenerates an inline Table Value Function for calculating root ID for a recursive FK field The function takes both the record ID and parent ID for optimization Returns SQL to create the function including DROP IF EXISTS
ProtectedgenerateGenerates joins to inline Table Value Functions for hierarchy and root ID calculation
ProtectedgenerateProtectedgenerateProtectedgenerateProtectedgenerateProtectedgenerateProtectedgenerateProtectedgenerateProtectedgetIdentifies entities that need their delete stored procedures regenerated due to cascade dependencies on entities that had schema changes.
ProtectedgetHelper to get the canonical hierarchy function definitions and metadata for an entity and recursive FK field.
ProtectedgetProtectedgetReturns entities from the modified and new entity lists that have custom base views (BaseViewGenerated === false), are included in the API, and are not virtual.
ProtectedgetGets entities that had schema changes from the ManageMetadataBase tracking. Returns a map of entity names to their IDs for entities that had update-affecting changes.
ProtectedhasProtectedhasCheck if an entity has native latitude/longitude fields marked with GeoLatitude/GeoLongitude ExtendedType. If both exist, the view should alias them directly instead of joining to RecordGeoCode.
MUST exclude virtual fields. The view-introspection pass creates virtual
EntityField rows for the __mj_Latitude/__mj_Longitude SELECT aliases
the JOIN path emits, then auto-assigns ExtendedType=GeoLatitude/Longitude
by name-pattern. Without this filter the next view regen flips to the
native path and tries to SELECT e.__mj_Longitude from the base table —
the column doesn't exist on the table, only on the view itself, so view
creation fails with "Invalid column name '__mj_Longitude'". This recurs
on any geo-eligible entity whose RecordGeoCode-based view ran once.
ProtectedisChecks if a field is part of a composite (multi-column) unique constraint. Returns true if the field participates in a unique index with 2+ columns. This is used to determine if a nullable FK should use DELETE instead of UPDATE during cascade operations, since setting to NULL could violate uniqueness.
ProtectedisProtectedloadSnapshot every CRUD routine currently in the database (one query) so generate-and-log can force-emit CREATE PROC for objects that vanished without the entity landing on newEntityList/modifiedEntityList.
ProtectedlogProtectedmarkMarks entities for delete stored procedure regeneration based on cascade dependencies. This should be called after metadata management and before SQL generation.
ProtectedorderOrders entities by their cascade delete dependencies using topological sort. Ensures that if Entity A's delete SP calls Entity B's update/delete SP, then Entity B is regenerated before Entity A.
All entities for name lookup
Set of entity IDs that need ordering
Array of entity IDs in dependency order
ProtectedprepareBuilds the once-per-run FieldSecurityRunContext and hands it to the dialect provider (whose permission emitters are synchronous string builders and cannot query). Three inputs, all read here:
MJ: Roles metadata rows with a non-blank SQLName, EXCLUDING
the standard UI/Developer/Integration roles: by decision D1a, field-security DENYs
target custom (DBA-created) roles only, so standard roles never enter
RoleSQLNameByID and Deny rows against them stay app-tier-enforced.sys.database_role_members). Protected principals default to the known service
logins (MJ_Connect, MJ_Connect_Dev) plus the configured CodeGen login; a DENY
to such a role would strip the column from the service login itself.sys.database_permissions entry (object- and
column-level) granted to a managed role, keyed by <schema>.<object>. This powers
the wipe-and-reassert reconciliation (decision D7) that finally makes permission
REMOVAL propagate: the historical model was assert-only, so a deleted
EntityPermission / Deny row left its GRANT/DENY in the database until the view
happened to be regenerated.SQL Server only (PostgreSQL emits no DB-tier field security, decision D2). Any failure degrades to a null context — emitters fall back to the historical grants-only output — with a prominent warning, never a failed run.
ProtectedstripProtectedvalidateProtectedwriteWrites a generated SQL file to disk only if the content has changed from the existing file. Returns true if the file was written (content changed or file is new), false if unchanged. This avoids false timestamp updates and unnecessary I/O.
This class is responsible for generating database level objects like views, stored procedures, permissions, etc. You can sub-class this class to create your own SQL generation logic or implement support for other databases. The base class implements support for SQL Server. In future versions of MJ, we will break out an abstract base class that has the skeleton of the logic and then the SQL Server version will be a sub-class of that abstract base class and other databases will be sub-classes of the abstract base class as well.