mirror of
https://github.com/yweber/lodel2.git
synced 2026-10-27 06:41:33 +01:00
Implemented alter table in sqlwrapper
Adding a column is implemented for mysql, postgresql and sqlite. Droping and altering a column is only implemented for mysql and postgresql for the moment. Quick&dirty tests has been done for sqlite and mysql only.
This commit is contained in:
parent
b82ccd7911
commit
6ab79eb412
2 changed files with 251 additions and 16 deletions
90
Database/sqlalter.py
Normal file
90
Database/sqlalter.py
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
|
||||
import sqlalchemy as sqla
|
||||
from sqlalchemy.ext.compiler import compiles
|
||||
|
||||
## @file sqlalter.py
|
||||
# This file defines all DDL (data definition langage) for the ALTER TABLE instructions
|
||||
#
|
||||
# It uses the SqlAlchemy compilation and quoting methos to generate SQL
|
||||
|
||||
|
||||
class AddColumn(sqla.schema.DDLElement):
|
||||
""" Defines the ddl for adding a column to a table """
|
||||
def __init__(self,table, column):
|
||||
""" Instanciate the DDL
|
||||
@param table sqlalchemy.Table: A sqlalchemy table object
|
||||
@param column sqlalchemy.Column: A sqlalchemy column object
|
||||
"""
|
||||
self.col = column
|
||||
self.table = table
|
||||
|
||||
@compiles(AddColumn, 'mysql')
|
||||
@compiles(AddColumn, 'postgresql')
|
||||
@compiles(AddColumn, 'sqlite')
|
||||
def visit_add_column(element, ddlcompiler, **kw):
|
||||
""" Compiles the AddColumn DDL for mysql, postgresql and sqlite"""
|
||||
prep = ddlcompiler.sql_compiler.preparer
|
||||
tname = prep.format_table(element.table)
|
||||
colname = prep.format_column(element.col)
|
||||
return 'ALTER TABLE %s ADD COLUMN %s %s'%(tname, colname, element.col.type)
|
||||
|
||||
@compiles(AddColumn)
|
||||
def visit_add_column(element, ddlcompiler, **kw):
|
||||
raise NotImplementedError('Add column not yet implemented for '+str(ddlcompiler.dialect.name))
|
||||
|
||||
class DropColumn(sqla.schema.DDLElement):
|
||||
""" Defines the DDL for droping a column from a table """
|
||||
def __init__(self, table, column):
|
||||
""" Instanciate the DDL
|
||||
@param table sqlalchemy.Table: A sqlalchemy table object
|
||||
@param column sqlalchemy.Column: A sqlalchemy column object representing the column to drop
|
||||
"""
|
||||
self.col = column
|
||||
self.table = table
|
||||
|
||||
@compiles(DropColumn,'mysql')
|
||||
@compiles(DropColumn, 'postgresql')
|
||||
def visit_drop_column(element, ddlcompiler, **kw):
|
||||
""" Compiles the DropColumn DDL for mysql & postgresql """
|
||||
prep = ddlcompiler.sql_compiler.preparer
|
||||
tname = prep.format_table(element.table)
|
||||
colname = prep.format_column(element.col)
|
||||
return 'ALTER TABLE %s DROP COLUMN %s'%(tname, colname)
|
||||
|
||||
@compiles(DropColumn)
|
||||
def visit_drop_column(element, ddlcompiler, **kw):
|
||||
raise NotImplementedError('Drop column not yet implemented for '+str(ddlcompiler.dialect.name))
|
||||
|
||||
class AlterColumn(sqla.schema.DDLElement):
|
||||
""" Defines the DDL for changing the type of a column """
|
||||
def __init__(self, table, column):
|
||||
""" Instanciate the DDL
|
||||
@param table sqlalchemy.Table: A sqlalchemy Table object
|
||||
@param column sqlalchemy.Column: A sqlalchemy Column object representing the new column
|
||||
"""
|
||||
self.col = column
|
||||
self.table = table
|
||||
|
||||
@compiles(AlterColumn, 'mysql')
|
||||
def visit_alter_column(element, ddlcompiler, **kw):
|
||||
""" Compiles the AlterColumn DDL for mysql """
|
||||
prep = ddlcompiler.sql_compiler.preparer
|
||||
tname = prep.format_table(element.table)
|
||||
colname = prep.format_column(element.col)
|
||||
return 'ALTER TABLE %s ALTER COLUMN %s %s'%(tname, colname, element.col.type)
|
||||
|
||||
@compiles(AlterColumn, 'postgresql')
|
||||
def visit_alter_column(element, ddlcompiler, **kw):
|
||||
""" Compiles the AlterColumn DDL for postgresql """
|
||||
prep = ddlcompiler.sql_compiler.preparer
|
||||
tname = prep.format_table(element.table)
|
||||
colname = prep.format_column(element.col)
|
||||
return 'ALTER TABLE %s ALTER COLUMN %s TYPE %s'%(tname, colname, element.col.type)
|
||||
|
||||
@compiles(AlterColumn)
|
||||
def visit_alter_column(element, ddlcompiler, **kw):
|
||||
raise NotImplementedError('Alter column not yet implemented for '+str(ddlcompiler.dialect.name))
|
||||
|
||||
|
|
@ -4,8 +4,11 @@ import re
|
|||
import logging as logger
|
||||
|
||||
import sqlalchemy as sqla
|
||||
from sqlalchemy.ext.compiler import compiles
|
||||
from django.conf import settings
|
||||
|
||||
from Database.sqlalter import *
|
||||
|
||||
#Logger config
|
||||
logger.getLogger().setLevel('DEBUG')
|
||||
#To be able to use dango confs
|
||||
|
|
@ -38,26 +41,32 @@ class SqlWrapper(object):
|
|||
##Wrapper instance list
|
||||
wrapinstance = dict()
|
||||
|
||||
def __init__(self, name="default", alchemy_logs=None, read_db = "default", write_db = "default"):
|
||||
def __init__(self, name=None, alchemy_logs=None, read_db = "default", write_db = "default"):
|
||||
""" Instanciate a new SqlWrapper
|
||||
@param name str: The wrapper name
|
||||
@param alchemy_logs bool: If true activate sqlalchemy logger
|
||||
@param read_db str: The name of the db conf
|
||||
@param write_db str: The name of the db conf
|
||||
|
||||
@todo Better use of name (should use self.cfg['wrapper'][name] to get engines configs
|
||||
@todo Is it a really good idea to store instance in class scope ? Maybe not !!
|
||||
"""
|
||||
|
||||
self.sqlalogging = False if alchemy_logs == None else bool(alchemy_logs)
|
||||
|
||||
self.name = name
|
||||
if name == None:
|
||||
self.name = read_db+'+'+write_db
|
||||
else:
|
||||
self.name = name
|
||||
|
||||
self.r_dbconf = read_db
|
||||
self.w_dbconf = write_db
|
||||
|
||||
self.checkConf() #raise if errors in configuration
|
||||
|
||||
if name in self.__class__.wrapinstance:
|
||||
logger.warning("A SqlWrapper with the name "+name+" allready exist. Replacing the old one by the new one")
|
||||
SqlWrapper.wrapinstance[name] = self
|
||||
if self.name in self.__class__.wrapinstance:
|
||||
logger.warning("A SqlWrapper with the name "+self.name+" allready exist. Replacing the old one by the new one")
|
||||
SqlWrapper.wrapinstance[self.name] = self
|
||||
|
||||
#Engine and wrapper initialisation
|
||||
self.r_engine = self._getEngine(True, self.sqlalogging)
|
||||
|
|
@ -66,8 +75,10 @@ class SqlWrapper(object):
|
|||
self.w_conn = None
|
||||
|
||||
|
||||
self.meta = None #TODO : use it to load all db schema in 1 request and don't load it each table instanciation
|
||||
self.metadata = None #TODO : use it to load all db schema in 1 request and don't load it each table instanciation
|
||||
self.meta_crea = None
|
||||
|
||||
logger.debug("New wrapper instance : <"+self.name+" read:"+str(self.r_engine)+" write:"+str(self.w_engine))
|
||||
pass
|
||||
|
||||
@property
|
||||
|
|
@ -75,6 +86,19 @@ class SqlWrapper(object):
|
|||
@property
|
||||
def engines_cfg(self): return self.__class__.ENGINES;
|
||||
|
||||
@property
|
||||
def meta(self):
|
||||
if self.metadata == None:
|
||||
self.renewMetaData()
|
||||
return self.metadata
|
||||
|
||||
def renewMetaData(self):
|
||||
""" (Re)load the database schema """
|
||||
if self.metadata == None:
|
||||
self.metadata = sqla.MetaData(bind=self.r_engine, reflect=True)
|
||||
else:
|
||||
self.metadata = sqla.MetaData(bind=self.r_engine, reflect=True)
|
||||
|
||||
@property
|
||||
def rconn(self):
|
||||
""" Return the read connection
|
||||
|
|
@ -88,7 +112,6 @@ class SqlWrapper(object):
|
|||
"""
|
||||
return self.getConnection(False)
|
||||
|
||||
|
||||
def getConnection(self, read):
|
||||
""" Return an opened connection
|
||||
@param read bool: If true return the reading connection
|
||||
|
|
@ -129,11 +152,17 @@ class SqlWrapper(object):
|
|||
@return None
|
||||
"""
|
||||
if read or read == None:
|
||||
self.r_conn.close()
|
||||
if self.r_conn == None:
|
||||
logger.info('Unable to close read connection : connection not opened')
|
||||
else:
|
||||
self.r_conn.close()
|
||||
self.r_conn = None
|
||||
|
||||
if not read or read == None:
|
||||
self.w_conn.close()
|
||||
if self.r_conn == None:
|
||||
logger.info('Unable to close write connection : connection not opened')
|
||||
else:
|
||||
self.w_conn.close()
|
||||
self.w_conn = None
|
||||
|
||||
def reconnect(self, read = None):
|
||||
|
|
@ -156,7 +185,8 @@ class SqlWrapper(object):
|
|||
"""
|
||||
if not isinstance(tname, str):
|
||||
return TypeError('Excepting a <class str> but got a '+str(type(tname)))
|
||||
return sqla.Table(tname, sqla.MetaData(), autoload_with=self.r_engine, autoload=True)
|
||||
#return sqla.Table(tname, self.meta, autoload_with=self.r_engine, autoload=True)
|
||||
return sqla.Table(tname, self.meta)
|
||||
|
||||
def _getEngine(self, read=True, sqlalogging = None):
|
||||
""" Return a sqlalchemy engine
|
||||
|
|
@ -194,7 +224,11 @@ class SqlWrapper(object):
|
|||
conn_str += '%s@%s/%s'%(user,host,cfg['NAME'])
|
||||
|
||||
|
||||
return sqla.create_engine(conn_str, encoding=edata['encoding'], echo=self.sqlalogging)
|
||||
ret = sqla.create_engine(conn_str, encoding=edata['encoding'], echo=self.sqlalogging)
|
||||
|
||||
logger.debug("Getting engine :"+str(ret))
|
||||
|
||||
return ret
|
||||
|
||||
@classmethod
|
||||
def getWrapper(c, name):
|
||||
|
|
@ -264,11 +298,14 @@ class SqlWrapper(object):
|
|||
"""
|
||||
self.meta_crea = sqla.MetaData()
|
||||
|
||||
logger.info("Running function createAllFromConf")
|
||||
for i,table in enumerate(schema):
|
||||
self.createTable(**table)
|
||||
|
||||
self.meta_crea.create_all(self.w_engine)
|
||||
self.meta_crea.create_all(bind = self.w_engine)
|
||||
logger.info("All tables created")
|
||||
self.meta_crea = None
|
||||
self.renewMetaData()
|
||||
pass
|
||||
|
||||
def createTable(self, name, columns, **kw):
|
||||
|
|
@ -295,6 +332,7 @@ class SqlWrapper(object):
|
|||
if crea_now:
|
||||
self.meta_crea.create_all(self.w_engine)
|
||||
|
||||
#logger.debug("Table '"+name+"' created")
|
||||
pass
|
||||
|
||||
def createColumn(self, **kwargs):
|
||||
|
|
@ -306,12 +344,13 @@ class SqlWrapper(object):
|
|||
- extra : a dict like { "primarykey":True, "nullable":False, "default":"test"...}
|
||||
@param **kwargs
|
||||
"""
|
||||
if not 'name' in kwargs or not 'type' in kwargs:
|
||||
if not 'name' in kwargs or ('type' not in kwargs and 'type_' not in kwargs):
|
||||
pass#ERROR
|
||||
|
||||
#Converting parameters
|
||||
kwargs['type_'] = self._strToSqlAType(kwargs['type'])
|
||||
del kwargs['type']
|
||||
if 'type_' not in kwargs and 'type' in kwargs:
|
||||
kwargs['type_'] = self._strToSqlAType(kwargs['type'])
|
||||
del kwargs['type']
|
||||
|
||||
if 'extra' in kwargs:
|
||||
#put the extra keys in kwargs
|
||||
|
|
@ -332,12 +371,12 @@ class SqlWrapper(object):
|
|||
del kwargs['primarykey']
|
||||
|
||||
|
||||
logger.debug('Column creation arguments : '+str(kwargs))
|
||||
res = sqla.Column(**kwargs)
|
||||
|
||||
if fk != None:
|
||||
res.append_foreign_key(fk)
|
||||
|
||||
#logger.debug("Column '"+kwargs['name']+"' created")
|
||||
return res
|
||||
|
||||
def _strToSqlAType(self, strtype):
|
||||
|
|
@ -357,6 +396,112 @@ class SqlWrapper(object):
|
|||
check_length = re.search(re.compile('VARCHAR\(([\d]+)\)', re.IGNORECASE), vstr)
|
||||
column_length = int(check_length.groups()[0]) if check_length else None
|
||||
return sqla.VARCHAR(length=column_length)
|
||||
|
||||
@classmethod
|
||||
def engineFamily(c, engine):
|
||||
""" Given an engine return the db family
|
||||
@see SqlWrapper::ENGINES
|
||||
@return A str or None
|
||||
"""
|
||||
for fam in c.ENGINES:
|
||||
if engine.driver == c.ENGINES[fam]['driver']:
|
||||
return fam
|
||||
return None
|
||||
|
||||
@property
|
||||
def wEngineFamily(self):
|
||||
""" Return the db family of the write engine
|
||||
@return a string or None
|
||||
"""
|
||||
return self.__class__.engineFamily(self.w_engine)
|
||||
@property
|
||||
def rEngineFamily(self):
|
||||
""" Return the db family of the read engine
|
||||
@return a string or None
|
||||
"""
|
||||
return self.__class__.engineFamily(self.r_engine)
|
||||
|
||||
def dropColumn(self, tname, colname):
|
||||
""" Drop a column from a table
|
||||
@param tname str|sqlalchemy.Table: The table name or a Table object
|
||||
@param colname str|sqlalchemy.Column: The column name or a column object
|
||||
@return None
|
||||
"""
|
||||
if tname not in self.meta.tables: #Useless ?
|
||||
raise NameError("The table '"+tname+"' dont exist")
|
||||
table = self.Table(tname)
|
||||
col = sqla.Column(colname)
|
||||
|
||||
ddl = DropColumn(table, col)
|
||||
sql = ddl.compile(dialect=self.w_engine.dialect)
|
||||
sql = str(sql)
|
||||
logger.debug("Executing SQL : '"+sql+"'")
|
||||
ret = bool(self.w_engine.execute(sql))
|
||||
|
||||
self.renewMetaData()
|
||||
return ret
|
||||
|
||||
|
||||
def addColumn(self, tname, colname, coltype):
|
||||
""" Add a column to a table
|
||||
@param tname str: The table name
|
||||
@param colname str: The column name
|
||||
@param coltype str: The new column type
|
||||
|
||||
@return True if query success False if it fails
|
||||
"""
|
||||
newcol = self.createColumn(name=colname, type_ = coltype)
|
||||
if tname not in self.meta.tables: #Useless ?
|
||||
raise NameError("The table '"+tname+"' dont exist")
|
||||
table = self.Table(tname)
|
||||
|
||||
ddl = AddColumn(table, newcol)
|
||||
sql = ddl.compile(dialect=self.w_engine.dialect)
|
||||
sql = str(sql)
|
||||
logger.debug("Executing SQL : '"+sql+"'")
|
||||
ret = bool(self.wconn.execute(sql))
|
||||
|
||||
self.renewMetaData()
|
||||
return ret
|
||||
|
||||
def alterColumn(self, tname, colname, col_newtype):
|
||||
""" Change the type of a column
|
||||
@param tname str: The table name
|
||||
@param colname str: The column name
|
||||
@param col_newtype str: The column new type
|
||||
|
||||
@return True if query successs False if it fails
|
||||
"""
|
||||
|
||||
if self.wEngineFamily == 'sqlite':
|
||||
raise NotImplementedError('AlterColumn not yet implemented for sqlite engines')
|
||||
|
||||
|
||||
col = self.createColumn(name=colname, type_=col_newtype)
|
||||
table = self.Table(tname)
|
||||
|
||||
typepref = 'TYPE ' if self.wEngineFamily == 'postgresql' else ''
|
||||
|
||||
query = 'ALTER TABLE %s ALTER COLUMN %s %s'%(table.name, col.name, typepref+col.type)
|
||||
|
||||
logger.debug("Executing SQL : '"+query+"'")
|
||||
|
||||
ret = bool(self.wconn.execute(query))
|
||||
|
||||
self.renewMetaData()
|
||||
return ret
|
||||
|
||||
def _debug__printSchema(self):
|
||||
""" Debug function to print the db schema """
|
||||
print(self.meta)
|
||||
for tname in self.meta.tables:
|
||||
self._debug__printTable(tname)
|
||||
|
||||
def _debug__printTable(self, tname):
|
||||
t = self.meta.tables[tname]
|
||||
tstr = 'Table : "'+tname+'" :\n'
|
||||
for c in t.c:
|
||||
tstr += '\t\t"'+c.name+'"('+str(c.type)+') \n'
|
||||
print(tstr)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue