Skip to content

Fix mixedcase and unsafe identifiers between pg and fb #35

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
234 changes: 171 additions & 63 deletions src/convert.c
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,10 @@ static bool foreign_expr_walker(Node *node,
static bool canConvertOp(OpExpr *oe, int firebird_version);
static bool is_builtin(Oid procid);

static const char *quote_fb_identifier_for_import(const char *ident);
static const char *quote_identifier_for_import(const char *ident, bool unsafe);

bool unSafeFirebirdIdentifier (const char *identifier);
bool unSafePostgresIdentifier (const char *identifier);

/**
* buildSelectSql()
Expand Down Expand Up @@ -371,7 +374,7 @@ buildWhereClause(StringInfo output,
* - verify all types can be converted to their PostgreSQL equivalents
*/
void
generateColumnMetadataQuery(StringInfoData *data_type_sql, char *fb_table_name)
generateColumnMetadataQuery(StringInfoData *data_type_sql, char *fb_safe_table_name)
{
appendStringInfo(data_type_sql,
" SELECT TRIM(rf.rdb$field_name) AS column_name,\n"
Expand Down Expand Up @@ -423,12 +426,64 @@ generateColumnMetadataQuery(StringInfoData *data_type_sql, char *fb_table_name)
" ON rf.rdb$field_source = f.rdb$field_name\n"
" WHERE TRIM(rf.rdb$relation_name) = '%s'\n"
" ORDER BY rf.rdb$field_position\n",
fb_table_name
fb_safe_table_name
);

return;
}

/**
* unSafeFirebirdIdentifier()
*
* checks if identifier contains space or identifier is not uppercase
* SIMPLIFICATION, not real UTF, POC
*/
bool unSafeFirebirdIdentifier (const char *identifier)
{
const char *ptr;
if (!((identifier[0] >= 'A' && identifier[0] <= 'Z') || identifier[0] == '_'))
return true;

for (ptr = identifier; *ptr; ptr++)
{
char ch = *ptr;

if (!(ch >= 'A' && ch <= 'Z') &&
!(ch >= '0' && ch <= '9') &&
(ch != '_'))
{
return true;
}
}
return false;
}

/**
* unSafePostgresIdentifier()
*
* checks if identifier contains space or identifier is not lowercase
* SIMPLIFICATION, not real UTF, POC
*/
bool unSafePostgresIdentifier (const char *identifier)
{
const char *ptr;
if (!((identifier[0] >= 'a' && identifier[0] <= 'z') || identifier[0] == '_'))
return true;

for (ptr = identifier; *ptr; ptr++)
{
char ch = *ptr;

if (!(ch >= 'a' && ch <= 'z') &&
!(ch >= '0' && ch <= '9') &&
(ch != '_'))
{
return true;
}
}
return false;
}

/**
* convertFirebirdObject()
*
Expand All @@ -437,45 +492,84 @@ generateColumnMetadataQuery(StringInfoData *data_type_sql, char *fb_table_name)
void
convertFirebirdObject(char *server_name, char *schema, char *object_name, char object_type, char *pg_name, bool import_not_null, bool updatable, FBresult *colres, StringInfoData *create_table)
{
const char *table_name;
const char *safe_pg_server_name;
bool unsafe_pg_server_name;
const char *safe_pg_schema_name;
bool unsafe_pg_schema_name;
const char *safe_table_name;
bool unsafe_table_name;

const char *safe_fb_column_name;
bool unsafe_fb_column_name = false;
const char *safe_pg_column_name;
bool unsafe_pg_column_name = false;

bool use_pg_name = false;
int colnr, coltotal;
List *table_options = NIL;

size_t identifierL = 0;
size_t cnL = 0;
char *cnOption;

/* Initialise table options list */
if (updatable == false)
table_options = lappend(table_options, "updatable 'false'");

unsafe_pg_server_name = unSafePostgresIdentifier(server_name);
safe_pg_server_name = quote_identifier_for_import(server_name, unsafe_pg_server_name);

unsafe_pg_schema_name = unSafePostgresIdentifier(schema);
safe_pg_schema_name = quote_identifier_for_import(schema, unsafe_pg_schema_name);

elog(DEBUG3, "safe_server_name: %s; safe_schema_name: %s;",
safe_pg_server_name,
safe_pg_schema_name
);

/*
* If the Firebird identifier is all lower-case, force "quote_identifier 'true'"
* as PostgreSQL won't know to quote it.
* XXX Currently we just check if the first character is lower case.
*/
table_name = quote_fb_identifier_for_import(object_name);

unsafe_table_name = unSafeFirebirdIdentifier(object_name);
safe_table_name = quote_identifier_for_import(object_name, unsafe_table_name);



elog(DEBUG3, "object_name: %s; table_name: %s; pg_name: %s",
elog(DEBUG3, "object_name: %s; safe_table_name: %s; pg_name: %s",
object_name,
table_name,
safe_table_name,
pg_name ? pg_name : "NULL");

if (table_name[0] == '"')
{
if (table_name[1] >= 'a' && table_name[1] <= 'z')
if (unsafe_table_name)
{
table_options = lappend(table_options, "quote_identifier 'true'");
}

identifierL = strlen(object_name);
cnL = strlen("table_name '");
cnOption = malloc(identifierL + cnL + 2);
cnOption[0] = '\0';
strcat(cnOption, "table_name '");
strcat(cnOption, object_name);
strcat(cnOption, "'");
table_options = lappend(table_options, (void*)cnOption);

elog(DEBUG3, "table identifier is fb unsafe %s", safe_table_name);
}
else if (pg_name != NULL)
{
/*
* If "pg_name" == "table_name", i.e. the non-quoted folder-to-upper-case
* If "pg_name" == "safe_table_name", i.e. the non-quoted folder-to-upper-case
* version used in the Firebird metadata query, then that implies
* the_name was quoted in the "LIMIT TO" clause, so we must
* quote it here.
*
* E.g. LIMIT TO ("BAR")
*/
if (strcmp(table_name, pg_name) == 0)
if (strcmp(safe_table_name, pg_name) == 0)
{
table_name = quote_identifier(table_name);
safe_table_name = quote_identifier(safe_table_name);
}
else
{
Expand All @@ -489,12 +583,14 @@ convertFirebirdObject(char *server_name, char *schema, char *object_name, char o
use_pg_name = true;
}
}
else if (unSafePostgresIdentifier(safe_table_name))
safe_table_name = quote_identifier(safe_table_name);

/* Generate SQL */
appendStringInfo(create_table,
"CREATE FOREIGN TABLE %s.%s (\n",
schema,
use_pg_name ? pg_name : table_name);
safe_pg_schema_name,
use_pg_name ? pg_name : safe_table_name);

coltotal = FQntuples(colres);

Expand All @@ -509,22 +605,41 @@ convertFirebirdObject(char *server_name, char *schema, char *object_name, char o

char *datatype;
char *colname = pstrdup(FQgetvalue(colres, colnr, 0));


unsafe_fb_column_name = unSafeFirebirdIdentifier(colname);
safe_fb_column_name = quote_identifier_for_import(colname, unsafe_fb_column_name);

const char *col_identifier = quote_fb_identifier_for_import(colname);
unsafe_pg_column_name = unSafePostgresIdentifier(colname);
safe_pg_column_name = quote_identifier_for_import(colname, unsafe_pg_column_name);

elog(DEBUG3, "column name %s unsafe pg %d unsafe fb %d", colname, unsafe_pg_column_name, unsafe_fb_column_name);

/*
* If the Firebird identifier is all lower-case, force "quote_identifier 'true'"
* If the Firebird identifier is unsafe, force "quote_identifier 'true'"
* as PostgreSQL won't know to quote it.
* XXX Currently we just check if the first character is lower case.
*/
if (col_identifier[0] == '"' && (col_identifier[1] >= 'a' && col_identifier[1] <= 'z'))
if (unsafe_fb_column_name)
{
column_options = lappend(column_options, "quote_identifier 'true'");

identifierL = strlen(colname);
cnL = strlen("column_name '");
cnOption = malloc(identifierL + cnL + 2);
cnOption[0] = '\0';
strcat(cnOption, "column_name '");
strcat(cnOption, colname);
strcat(cnOption, "'");
column_options = lappend(column_options, (void*)cnOption);
}

/* Column name and datatype */
datatype = FQgetvalue(colres, colnr, 2);

safe_pg_column_name = unsafe_pg_column_name ? quote_identifier(colname) : colname;
appendStringInfo(create_table,
" %s %s",
col_identifier,
safe_pg_column_name,
datatype);


Expand Down Expand Up @@ -583,7 +698,7 @@ convertFirebirdObject(char *server_name, char *schema, char *object_name, char o

appendStringInfo(create_table,
") SERVER %s",
server_name);
safe_pg_server_name);

if (table_options != NIL)
{
Expand Down Expand Up @@ -785,43 +900,53 @@ quote_fb_identifier(const char *ident, bool quote_ident)
return quoted_ident;
}


/**
* quote_fb_identifier_for_import()
* quote_identifier()
*
* Given a Firebird relation name, determine whether it would
* be quoted in Firebird, i.e. contains characters other than
* ASCII capital letters, digits and underscores.
* Add ""
*/
static const char *
quote_fb_identifier_for_import(const char *ident)
const char *quote_identifier(const char *ident)
{
int nquotes = 0;
bool safe;
const char *ptr;
char *result;
char *optr;

for (ptr = ident; *ptr; ptr++)
{
if (*ptr == '"')
nquotes++;
}

result = (char *) palloc(strlen(ident) + nquotes + 2 + 1);

safe = ((ident[0] >= 'A' && ident[0] <= 'Z') || ident[0] == '_');

optr = result;
*optr++ = '"';
for (ptr = ident; *ptr; ptr++)
{
char ch = *ptr;

if ((ch >= 'A' && ch <= 'Z') ||
(ch >= '0' && ch <= '9') ||
(ch == '_'))
{
/* okay */
}
else
{
safe = false;
if (ch == '"')
nquotes++;
}
if (ch == '"')
*optr++ = '"';
*optr++ = ch;
}
*optr++ = '"';
*optr = '\0';

return result;
}

/**
* quote_identifier_for_import()
*
* Given a object name, determine whether it would be quoted
* both for Postgres or Firebird depends on source of safe flague
*/
static const char *
quote_identifier_for_import(const char *ident, bool unsafe)
{
bool safe;
safe = !unsafe;

if (safe)
{
Expand Down Expand Up @@ -851,24 +976,7 @@ quote_fb_identifier_for_import(const char *ident)

if (safe)
return ident; /* no change needed */


result = (char *) palloc(strlen(ident) + nquotes + 2 + 1);

optr = result;
*optr++ = '"';
for (ptr = ident; *ptr; ptr++)
{
char ch = *ptr;

if (ch == '"')
*optr++ = '"';
*optr++ = ch;
}
*optr++ = '"';
*optr = '\0';

return result;
return quote_identifier(ident);
}

/**
Expand Down