Safe Haskell | None |
---|---|
Language | Haskell2010 |
Postgres SQL Rename Identifiers
- Prefix table names with underscores to avoid issues where column names and tables conflict.
This can happen because we give columns and tables the name
root
for some reason, and that can trip uprow_to_json
. See https://github.com/PostgREST/postgrest/issues/993#issuecomment-340377813. An alternative solution would be to not create aTableAlias
with the nameroot
, but that seemed a bit complicated for me to do at the time. - Bypass the Postgres limitation of truncating identifiers to 63 characters long by prepending they identifier's md5 hash when they are longer than 63 characters.
We do both operations in the same traversal for performance reasons, but a simpler
implementation of (1) would be transformBi prefixHash
from the uniplate or the
generic-plate package.
See Postgres docs: https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
Synopsis
- renameIdentifiers :: Select -> Select
- renameIdentifiersSelectWith :: SelectWithG Select -> SelectWithG Select
- prefixHash :: Identifier -> Identifier
- renameTablesAndLongIdentifiers :: Select -> Select
- renameTablesAndLongIdentifiersWith :: SelectWithG Select -> SelectWithG Select
- runMyState :: MyState a -> a
- noTables :: TableNames
- newtype TableNames = TableNames {
- _tables :: HashSet Identifier
- type MyState = State TableNames
- mkPrefixedName :: Identifier -> Identifier
- addAliasAndPrefixHash :: TableAlias -> MyState TableAlias
- getTableNameAndPrefixHash :: Identifier -> MyState Identifier
- restoringTables :: MyState a -> MyState a
- uSelectWith :: SelectWithG Select -> MyState (SelectWithG Select)
- uSelect :: Select -> MyState Select
- uFromExp :: FromExp -> MyState FromExp
- uFromItem :: FromItem -> MyState FromItem
- uFunctionExp :: FunctionExp -> MyState FunctionExp
- uFunctionArgs :: FunctionArgs -> MyState FunctionArgs
- uFunctionAlias :: FunctionAlias -> MyState FunctionAlias
- uJoinExp :: JoinExpr -> MyState JoinExpr
- uJoinCond :: JoinCond -> MyState JoinCond
- uBoolExp :: BoolExp -> MyState BoolExp
- uSqlExp :: SQLExp -> MyState SQLExp
- uOrderBy :: OrderByExp -> MyState OrderByExp
- prefixHashTableAlias :: TableAlias -> TableAlias
- prefixHashColumnAlias :: ColumnAlias -> ColumnAlias
API
renameIdentifiers :: Select -> Select Source #
Prefix table names with undescores and rename long identifiers.
renameIdentifiersSelectWith :: SelectWithG Select -> SelectWithG Select Source #
prefix table names with undescores and rename long identifiers.
Prefix long identifiers
We are traversing the query transform Identifier
s in a query that are
longer than 63 characters by prefixing them with their md5 hash.
This works because:
- Database table references and column references also use
Identifier
, but they cannot be more than 63 characters long, so we will never transform those cases. - The md5 hash is a 32 characters long deterministic representation of the identifier, so even when truncated by postgres, it will always be enough to identify the identifiers.
- It is possible in theory for to identifiers to produce the same hash, but extremely unlikely and I don't think we'll ever run into such a case.
Note that we could in theory replace the identifier with a hash, but we prefix the hash instead for our benefit as developers - if we need to read the query at some point we can look at the rest of the identifier ignoring the hash for a readable representation.
prefixHash :: Identifier -> Identifier Source #
Prefix md5 hash if identifier length is over 63 characters. We assume (rightly) that identifiers with names longer than 63 characters are not database table columns, and are made by us using aliases, so we should be free to rename them.
Prefix table names with underscore
API
Data types
runMyState :: MyState a -> a Source #
type MyState = State TableNames Source #
Utilities
mkPrefixedName :: Identifier -> Identifier Source #
attach a prefix to an identifier
addAliasAndPrefixHash :: TableAlias -> MyState TableAlias Source #
Add the alias to the set and return a prefixed alias.
getTableNameAndPrefixHash :: Identifier -> MyState Identifier Source #
Search for the identifier in the table names set and return a prefixed identifier if found, or the original identifier if not found in the set.
restoringTables :: MyState a -> MyState a Source #
Run an action that might change the tables names set and discard the changes made to the set.
Algorithm
uSelectWith :: SelectWithG Select -> MyState (SelectWithG Select) Source #
We run the algorithm on each CTE separately and discard the table names set, then we run the algorithm on the main select and return that result (with the table names found in scope).
uSelect :: Select -> MyState Select Source #
We go in order of each component in the select, starting with
the from and CTE clauses (as those introduce new table names to scope).
We return a transformed Select
(with the table names).
uFromExp :: FromExp -> MyState FromExp Source #
Transform every from_item
.
Potentially introduces a new alias.
uFromItem :: FromItem -> MyState FromItem Source #
Transform a single from_item
.
Potentially introduces a new alias.
uFunctionExp :: FunctionExp -> MyState FunctionExp Source #
Transform a function call expression.
uFunctionArgs :: FunctionArgs -> MyState FunctionArgs Source #
Transform function call arguments.
uFunctionAlias :: FunctionAlias -> MyState FunctionAlias Source #
Transform a function call alias.
uJoinExp :: JoinExpr -> MyState JoinExpr Source #
Transform join expressions. Potentially introduces a new alias.
uJoinCond :: JoinCond -> MyState JoinCond Source #
Transform Join condition. ON
join condition might contain references
to table names and aliases.
uBoolExp :: BoolExp -> MyState BoolExp Source #
Transform boolean expression.
The boolean expression structure does not contain a table name currently,
So we look for SQLExp
s and transform those, as those may contain table
names and aliases.
We discard table names that might be introduced here because we don't use them outside of the boolean expression.
uSqlExp :: SQLExp -> MyState SQLExp Source #
Transform a SQL expression. We look for table names and aliases and rename them if needed. SQL expressions do not introduce new table aliases, so we discard the new aliases that might be generated here.
uOrderBy :: OrderByExp -> MyState OrderByExp Source #
Transform order by clauses.
Since order by does not introduce new aliases we can discard the new names
that might be added, this is already done by uSqlExp
though.
prefixHashTableAlias :: TableAlias -> TableAlias Source #
Prefix a table alias with a hash if needed.
prefixHashColumnAlias :: ColumnAlias -> ColumnAlias Source #
Prefix a column alias with a hash if needed.