0

I have that error

File "/home/darek/PycharmProjects/Small_programs/Analiza_danych_nauka/db_manager.py", line 52, in _execute self._cur.execute(query) psycopg2.errors.SyntaxError: syntax error at end of input LINE 1: ... EXISTS "companys" ("id SERIAL PRIMARY KEY", "name VARCHAR")

I start this code like

db.create_table('companys', ['id SERIAL PRIMARY KEY', 'name VARCHAR'])

next

    def create_table(self, table, columns):
    create_query = sql.SQL("CREATE TABLE IF NOT EXISTS {} ({})").format(
        sql.Identifier(table),
        sql.SQL(', ').join(map(sql.Identifier, columns))
    )
    self._execute(create_query)

and line from error msg

    def connect(self):
    try:
        conn = psycopg2.connect(
            user=self.user,
            password=self.password,
            host=self.host,
            port=self.port,
            dbname=self.dbname)
        # cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
        cur = conn.cursor()
        pp(
            '------------------------------------------------------------'
            '\n-# PostgreSQL connection & transaction is ACTIVE\n'
        )
    except (Exception, psycopg2.Error) as error:
        print(error, sep='\n')
        sys.exit()
    else:
        self._conn = conn
        self._cur = cur
        self._counter = 0

def _check_connection(self):
    try:
        self._conn
    except AttributeError:
        print('ERROR: NOT Connected to Database')
        sys.exit()

def _execute(self, query, Placeholder_value=None):
    self._check_connection()
    if Placeholder_value == None or None in Placeholder_value:
        self._cur.execute(query) # 52 line from error msg
        print('-# ' + query.as_string(self._conn) + ';\n')
    else:
        self._cur.execute(query, Placeholder_value)
        print('-# ' + query.as_string(self._conn) % Placeholder_value + ';\n')
darek_82
  • 361
  • 1
  • 3
  • 13
  • In order to check what you are executing using your code before executing it just print it: `print(create_query) \n self._execute(create_query)` – Umut TEKİN Jul 27 '22 at 11:39
  • before self._cur.execute(query) printed query gave result Composed([SQL('CREATE TABLE IF NOT EXISTS '), Identifier('companys'), SQL(' ('), Composed([Identifier('id SERIAL PRIMARY KEY'), SQL(', '), Identifier('name VARCHAR')]), SQL(')')]) – darek_82 Jul 27 '22 at 13:14
  • The issue is this `['id SERIAL PRIMARY KEY', 'name VARCHAR']`. `map(sql.Identifier, columns)` can only refer to the column names, not the column type combination. The error. "("id SERIAL PRIMARY KEY", "name VARCHAR")" is showing the problem: `"id SERIAL PRIMARY KEY"`. The double quotes around the column/type instead of just the column. – Adrian Klaver Jul 27 '22 at 15:03

2 Answers2

2

A suggestion. NOTE: This uses sql.SQL() which does not escape strings and therefore is a SQL injection risk. Make sure you validate the input. It also involves a change in the column listing:

col_list = [
{"col_name": "id", "col_type": "serial", "col_constraint": "primary key"},
{"col_name": "name", "col_type": "varchar", "col_constraint": None}
]

composed_list = []
for col in col_list:
    col_cmp = []
    col_cmp.append(sql.Identifier(col["col_name"]))
    col_cmp.append(sql.SQL(" "))
    col_cmp.append(sql.SQL(col["col_type"]))
    if col.get("col_constraint"):
        col_cmp.append(sql.SQL(" ")) 
        col_cmp.append(sql.SQL(col["col_constraint"])) 
    composed_list.append(sql.Composed(col_cmp))

base_sql = sql.SQL(
"CREATE TABLE IF NOT EXISTS {table} ({fields})").\
format(table=sql.Identifier("companys"), 
fields=sql.SQL(",").join(composed_list)
)

print(base_sql.as_string(con))                                                                                                                                           
CREATE TABLE IF NOT EXISTS "companys" ("id" serial primary key,"name" varchar)

cur.execute(base_sql)                                                                                                                                                    
con.commit()

--psql
 \d companys
                                 Table "public.companys"
 Column |       Type        | Collation | Nullable |               Default                
--------+-------------------+-----------+----------+--------------------------------------
 id     | integer           |           | not null | nextval('companys_id_seq'::regclass)
 name   | character varying |           |          | 
Indexes:
    "companys_pkey" PRIMARY KEY, btree (id)
   
Adrian Klaver
  • 15,886
  • 2
  • 17
  • 28
  • If I don' t read wrong from the [document](https://www.psycopg.org/docs/sql.html), on the contrary it is safer to use against SQL injection. After your answer, I searched a little bit more and found [this](https://stackoverflow.com/questions/46770201/compose-dynamic-sql-string-with-psycopg2). For DDL statements, psycopg2' s "sql" module is not right choice I think. Self torture :). Yet, it does what is asked. Thanks. – Umut TEKİN Jul 28 '22 at 18:42
  • From the docs: "The string doesn’t undergo any form of escaping, so it is not suitable to represent variable identifiers or values: ..." which is what is being done here. I put the caution up just to be on safe side, in case someone borrows this for another use. I don't do DDL in Python, this was a test to see what could be achieved. I use [Sqitch](https://sqitch.org/) and SQL scripts to build and maintain DDL statements. Allows for versioning, schema migration, schema rollbacks, etc. – Adrian Klaver Jul 28 '22 at 18:48
  • Yep, we are on the same side. In order to complete, the following parts explain why it is safe against SQL inejction: "The string doesn’t undergo any form of escaping, so it is not suitable to represent variable identifiers or values: `you should only use it to pass constant strings representing templates or snippets of SQL statements`; `use other objects such as Identifier or Literal to represent variable parts`." – Umut TEKİN Jul 28 '22 at 19:00
  • @AdrianKlaver it worked wonderfully for me, I can't thank you enough!!!! – Kyle Sponable Aug 13 '22 at 20:50
0

After checking SQL class of psycopg2 module as Adrian stated you can provide column names. Because of compasable subclass it parses columns names between doubles quotes(single quote if you use "Literal"). By the way, in order to print the query we should have used as_string method :). Here:

import psycopg2
from psycopg2 import sql
conn_string = "host='localhost' dbname='postgres' user='postgres' password='123123'"
cursor = conn.cursor()
query = sql.SQL("CREATE TABLE IF NOT EXISTS {} ({})").format(sql.Identifier('companys'), sql.SQL(', ').join(map(sql.Identifier, ['id SERIAL PRIMARY KEY', 'name VARCHAR'])))
print(query.as_string(conn))
CREATE TABLE IF NOT EXISTS "companys" ("id SERIAL PRIMARY KEY", "name VARCHAR")
cursor.execute(query)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
psycopg2.ProgrammingError: syntax error at end of input
LINE 1: ... EXISTS "companys" ("id SERIAL PRIMARY KEY", "name VARCHAR")
                                                                       ^


s1 = sql.Literal('id SERIAL PRIMARY KEY')
s2 = sql.Literal('name VARCHAR')
query = sql.SQL("CREATE TABLE IF NOT EXISTS {} ({})").format(sql.Identifier('companys'), sql.SQL(', ').join([s1, s2]))
print(query.as_string(conn))
CREATE TABLE IF NOT EXISTS "companys" ('id SERIAL PRIMARY KEY', 'name VARCHAR')
cursor.execute(query)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
psycopg2.ProgrammingError: syntax error at or near "'id SERIAL PRIMARY KEY'"
LINE 1: CREATE TABLE IF NOT EXISTS "companys" ('id SERIAL PRIMARY KE...

I think, "psycopg2.sql – SQL string" generally are being used to prevent SQL injection in any case of data retrieve operation. You can try to convert you own environment to fit this solution but it is a tailored solution. Because this is a DDL operation you can safely use:

cursor.execute(query)
Umut TEKİN
  • 856
  • 1
  • 9
  • 19
  • Sow I pass only names like below but how I pass his type? db.create_table('companys', ["id", "name"]) – darek_82 Jul 28 '22 at 15:12
  • Please check the solution I gave you. Also, you cannot use as `db.create_table('companys', ["id", "name"])`. Use `cursor.execute('CREATE TABLE IF NOT EXISTS "companys" (id SERIAL PRIMARY KEY, name VARCHAR)')` – Umut TEKİN Jul 28 '22 at 15:41
  • I use it on begining now I want use sql module. Here is example something like my problem but I don't understand how from CSV is taken values "name" text where it is not a string becouse phrase text is outside quotes https://stackoverflow.com/questions/54714576/format-create-table-query-with-list-of-identifiers – darek_82 Jul 28 '22 at 18:18
  • Because of `as_string`: "Use the method as_string(context) to build the columns list and convert all string arguments to" [from](https://stackoverflow.com/questions/54714576/format-create-table-query-with-list-of-identifiers) that I gave to you. `column_types = [sql.Identifier(c).as_string(cur) + " text" for c in columns]` – Umut TEKİN Jul 28 '22 at 19:51