From 689cb7c016d51f030ad09d5433f74485e6b09e0c Mon Sep 17 00:00:00 2001 From: yumoqing Date: Tue, 14 May 2019 10:47:28 +0800 Subject: [PATCH] first commit --- README.md | 121 ++++++ build/lib/sqlor/__init__.py | 0 build/lib/sqlor/aiomysqlor.py | 7 + build/lib/sqlor/aiopostgresqlor.py | 8 + build/lib/sqlor/dbpools.py | 277 +++++++++++++ build/lib/sqlor/ddl_template_mysql.py | 55 +++ build/lib/sqlor/ddl_template_oracle.py | 49 +++ build/lib/sqlor/ddl_template_postgresql.py | 45 ++ build/lib/sqlor/ddl_template_sqlserver.py | 51 +++ build/lib/sqlor/mssqlor.py | 175 ++++++++ build/lib/sqlor/mysqlor.py | 179 ++++++++ build/lib/sqlor/oracleor.py | 131 ++++++ build/lib/sqlor/postgresqlor.py | 199 +++++++++ build/lib/sqlor/sor.py | 454 +++++++++++++++++++++ build/lib/sqlor/sqlite3or.py | 100 +++++ build/lib/sqlor/sqlor.py | 393 ++++++++++++++++++ dist/sqlor-0.0.1-py3.7.egg | Bin 0 -> 56420 bytes setup.py | 42 ++ sqlor.egg-info/PKG-INFO | 14 + sqlor.egg-info/SOURCES.txt | 21 + sqlor.egg-info/dependency_links.txt | 1 + sqlor.egg-info/top_level.txt | 1 + sqlor/.dbpools.py.swp | Bin 0 -> 16384 bytes sqlor/__init__.py | 0 sqlor/aiomysqlor.py | 7 + sqlor/aiopostgresqlor.py | 8 + sqlor/dbpools.py | 277 +++++++++++++ sqlor/ddl_template_mysql.py | 55 +++ sqlor/ddl_template_oracle.py | 49 +++ sqlor/ddl_template_postgresql.py | 45 ++ sqlor/ddl_template_sqlserver.py | 51 +++ sqlor/mssqlor.py | 175 ++++++++ sqlor/mysqlor.py | 179 ++++++++ sqlor/oracleor.py | 131 ++++++ sqlor/postgresqlor.py | 199 +++++++++ sqlor/sor.py | 454 +++++++++++++++++++++ sqlor/sqlite3or.py | 100 +++++ test/primary.py | 47 +++ test/t1.py | 44 ++ test/t2.py | 42 ++ test/test.py | 42 ++ 41 files changed, 4228 insertions(+) create mode 100644 README.md create mode 100644 build/lib/sqlor/__init__.py create mode 100644 build/lib/sqlor/aiomysqlor.py create mode 100644 build/lib/sqlor/aiopostgresqlor.py create mode 100644 build/lib/sqlor/dbpools.py create mode 100644 build/lib/sqlor/ddl_template_mysql.py create mode 100644 build/lib/sqlor/ddl_template_oracle.py create mode 100644 build/lib/sqlor/ddl_template_postgresql.py create mode 100644 build/lib/sqlor/ddl_template_sqlserver.py create mode 100644 build/lib/sqlor/mssqlor.py create mode 100644 build/lib/sqlor/mysqlor.py create mode 100644 build/lib/sqlor/oracleor.py create mode 100644 build/lib/sqlor/postgresqlor.py create mode 100644 build/lib/sqlor/sor.py create mode 100644 build/lib/sqlor/sqlite3or.py create mode 100644 build/lib/sqlor/sqlor.py create mode 100644 dist/sqlor-0.0.1-py3.7.egg create mode 100755 setup.py create mode 100644 sqlor.egg-info/PKG-INFO create mode 100644 sqlor.egg-info/SOURCES.txt create mode 100644 sqlor.egg-info/dependency_links.txt create mode 100644 sqlor.egg-info/top_level.txt create mode 100644 sqlor/.dbpools.py.swp create mode 100644 sqlor/__init__.py create mode 100644 sqlor/aiomysqlor.py create mode 100644 sqlor/aiopostgresqlor.py create mode 100644 sqlor/dbpools.py create mode 100644 sqlor/ddl_template_mysql.py create mode 100644 sqlor/ddl_template_oracle.py create mode 100644 sqlor/ddl_template_postgresql.py create mode 100644 sqlor/ddl_template_sqlserver.py create mode 100644 sqlor/mssqlor.py create mode 100644 sqlor/mysqlor.py create mode 100644 sqlor/oracleor.py create mode 100644 sqlor/postgresqlor.py create mode 100644 sqlor/sor.py create mode 100644 sqlor/sqlite3or.py create mode 100644 test/primary.py create mode 100644 test/t1.py create mode 100644 test/t2.py create mode 100644 test/test.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..946acb7 --- /dev/null +++ b/README.md @@ -0,0 +1,121 @@ +# SQLOR + +SQLOR is a database api for python3, it is base on the python's DBAPI2 + +## Feature + +* Multiple database supported(Oracle, MySql, Postgresql, SQL Server +* Both asynchronous API & synchronous API supported +* Connection pools +* Connection life cycle managements +* Easy using API +* Resources(connection object, cursor object) automatic recycled + + +## requirements + +* python 3.5 or above +* asyncio +* Oracle DBAPI2 driver(cx_Oracle) +* MySQL DBAPI2 driver(mysql-connector) +* Postgresql DBAPI2 driver(psycopg2-binrary) +* Asynchronous MySQL driver(aiomysql) +* Asynchronous Postgresql driver(aiopg) +* Other driver can be easy integreated + +## Using + +``` +import asyncio + +from sqlor.dbpools import DBPools + +dbs={ + "aiocfae":{ + "driver":"aiomysql", + "async_mode":True, + "coding":"utf8", + "dbname":"cfae", + "kwargs":{ + "user":"test", + "db":"cfae", + "password":"test123", + "host":"localhost" + } + }, + "cfae":{ + "driver":"mysql.connector", + "coding":"utf8", + "dbname":"cfae", + "kwargs":{ + "user":"test", + "db":"cfae", + "password":"test123", + "host":"localhost" + } + } +} + +loop = asyncio.get_event_loop() +pool = DBPools(dbs,loop=loop) + +async def testfunc(): + @pool.runSQL + def sql(db,ns,callback): + return { + "sql_string":"select * from product", + } + x = await sql('cfae',{},print) + x = await sql('aiocfae',{},print) + +loop.run_until_complete(testfunc()) +``` + +## API + + +### Databases description data(dbdesc) + +sqlor uses a dbdesc data(databases description data) which description +how many databases and what database will using, and them connection parameters to create a dbpools objects + +dbdesc data is a dict data, format of the dbdesc as follow: +``` +{ + "aiocfae":{ # name to identify a database connect + "driver":"aiomysql", # database dbapi2 driver package name + "async_mode":True, # indicte this connection is asynchronous mode + "coding":"utf8", # charset coding + "dbname":"cfae", # database real name + "kwargs":{ # connection parameters + "user":"test", + "db":"cfae", + "password":"test123", + "host":"localhost" + } + }, + "cfae":{ + "driver":"mysql.connector", + "coding":"utf8", + "dbname":"cfae", + "kwargs":{ + "user":"test", + "db":"cfae", + "password":"test123", + "host":"localhost" + } + } +} + +``` +sqlor can using multiple databases and difference databases by using difference database driver + +### sql description data + + +## class + +### DBPools + +### SQLor + diff --git a/build/lib/sqlor/__init__.py b/build/lib/sqlor/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/build/lib/sqlor/aiomysqlor.py b/build/lib/sqlor/aiomysqlor.py new file mode 100644 index 0000000..26c6fb7 --- /dev/null +++ b/build/lib/sqlor/aiomysqlor.py @@ -0,0 +1,7 @@ +from .mysqlor import MySqlor + +class AioMysqlor(MySqlor): + @classmethod + def isMe(self,name): + return name=='aiomysql' + diff --git a/build/lib/sqlor/aiopostgresqlor.py b/build/lib/sqlor/aiopostgresqlor.py new file mode 100644 index 0000000..7094ebc --- /dev/null +++ b/build/lib/sqlor/aiopostgresqlor.py @@ -0,0 +1,8 @@ + +from .postgresqlor import PostgreSQLor +class AioPostgresqlor(PostgreSQLor): + @classmethod + def isMe(self,name): + return name=='aiopg' + + diff --git a/build/lib/sqlor/dbpools.py b/build/lib/sqlor/dbpools.py new file mode 100644 index 0000000..6ee08c0 --- /dev/null +++ b/build/lib/sqlor/dbpools.py @@ -0,0 +1,277 @@ + +import asyncio +from queue import Queue +from functools import wraps +import codecs + +from appPublic.myImport import myImport +from appPublic.dictObject import DictObject +from appPublic.Singleton import SingletonDecorator +from appPublic.myjson import loadf +from appPublic.jsonConfig import getConfig + +from .sor import SQLor +from .mssqlor import MsSqlor +from .oracleor import Oracleor +from .sqlite3or import SQLite3or +from .mysqlor import MySqlor +from .aiomysqlor import AioMysqlor +from .aiopostgresqlor import AioPostgresqlor + + +def sqlorFactory(dbdesc): + driver = dbdesc.get('driver',dbdesc) + def findSubclass(name,klass): + for k in klass.__subclasses__(): + if k.isMe(name): + return k + k1 = findSubclass(name,k) + if k1 is not None: + return k1 + return None + k = findSubclass(driver,SQLor) + if k is None: + return SQLor(dbdesc=dbdesc) + return k(dbdesc=dbdesc) + +def sqlorFromFile(dbdef_file,coding='utf8'): + dbdef = loadf(dbdef_file) + return sqlorFactory(dbdef) + +class LifeConnect: + __conndict = {} + def __init__(self,connfunc,kw,use_max=1000,async_mode=False): + self.connfunc = connfunc + self.async_mode = async_mode + self.use_max = use_max + self.kw = kw + self.conn = None + + def print(self): + print(self.use_max) + print(self.conn) + + async def _mkconn(self): + if self.async_mode: + self.conn = await self.connfunc(**self.kw) + else: + self.conn = self.connfunc(**self.kw) + self.use_cnt = 0 + self.__conndict[self.conn] = self + + async def use(self): + if self.conn is None: + await self._mkconn() + conn = self.conn + if await self.testok(): + return conn + del self.__conndict[conn] + await self._mkconn() + + @classmethod + async def free(self,conn): + lc = self.__conndict[conn] + lc.use_cnt = lc.use_cnt + 1 + if lc.use_cnt >= lc.use_max: + await lc.conn.close() + await lc._mkcomm() + return lc + + async def testok(self): + if self.async_mode: + async with self.conn.cursor() as cur: + try: + await cur.execute('select 1 as cnt') + return True + except: + return False + else: + cur = self.conn.cursor() + try: + cur.execute('select 1 as cnt') + r = cur.fetchall() + return True + except: + return False + finally: + cur.close() + +class ConnectionPool(object): + def __init__(self,dbdesc,loop): + self.dbdesc = dbdesc + self.async_mode = dbdesc.get('async_mode',False) + self.loop = loop + self.driver = myImport(self.dbdesc['driver']) + self.maxconn = dbdesc.get('maxconn',5) + self.maxuse = dbdesc.get('maxuse',1000) + self._pool = Queue(self.maxconn) + self._fillPool() + self.using = [] + self.use_cnt = 0 + self.max_use = 1000 + + def _fillPool(self): + for i in range(self.maxconn): + lc = self.connect() + i = i + 1 + + def connect(self): + lc = LifeConnect(self.driver.connect,self.dbdesc['kwargs'], + use_max=self.maxuse,async_mode=self.async_mode) + self._pool.put(lc) + return lc + + def isEmpty(self): + return self._pool.empty() + + def isFull(self): + return self._pool.full() + + async def aquire(self): + lc = self._pool.get() + self.using.append(lc) + conn = await lc.use() + return conn + + async def release(self,conn): + lc = await LifeConnect.free(conn) + self.using = [c for c in self.using if c != lc ] + self._pool.put(lc) + +@SingletonDecorator +class DBPools: + def __init__(self,databases={},max_connect=10,loop=None): + if loop is None: + loop = asyncio.get_event_loop() + self.loop = loop + self._cpools = {} + self.databases = databases + + def addDatabase(self,name,desc): + self.databases[name] = desc + + async def getSqlor(self,name): + desc = self.databases.get(name) + sor = sqlorFactory(desc) + sor.name = name + a,conn,cur = await self._aquireConn(name) + sor.setCursor(a,conn,cur) + return sor + + async def freeSqlor(self,sor): + await self._releaseConn(sor.name,sor.conn,sor.cur) + + async def _aquireConn(self,dbname): + p = self._cpools.get(dbname) + if p == None: + p = ConnectionPool(self.databases.get(dbname),self.loop) + self._cpools[dbname] = p + conn = await p.aquire() + if self.isAsyncDriver(dbname): + cur = await conn.cursor() + else: + cur = conn.cursor() + return self.isAsyncDriver(dbname),conn,cur + + def isAsyncDriver(self,dbname): + ret = self.databases[dbname].get('async_mode',False) + return ret + + async def _releaseConn(self,dbname,conn,cur): + if self.isAsyncDriver(dbname): + await cur.close() + else: + try: + cur.fetchall() + except: + pass + cur.close() + p = self._cpools.get(dbname) + if p == None: + raise Exception('database (%s) not connected'%dbname) + await p.release(conn) + + def inSqlor(self,func): + @wraps(func) + async def wrap_func(sor,dbname,*args,**kw): + sor = await self.getSqlor(dbname) + try: + ret = await func(sor,dbname,*args,**kw) + return ret + except Exception as e: + print('error',sor) + raise e + finally: + await self.freeSqlor(sor) + + return wrap_func + + def runSQL(self,func): + @wraps(func) + async def wrap_func(dbname,NS,callback,**kw): + sor = await self.getSqlor(dbname) + desc = func(dbname,NS,callback,**kw) + try: + ret = await sor.runSQL(desc,NS,callback,**kw) + except Exception as e: + print('error:',e) + raise e + finally: + await self.freeSqlor(sor) + return wrap_func + + def runSQLPaging(self,func): + @wraps(func) + async def wrap_func(dbname,NS,**kw): + sor = await self.getSqlor(dbname) + desc = func(dbname,NS,**kw) + total = await sor.record_count(desc,NS) + recs = await sor.pagingdata(desc,NS) + data = { + "total":total, + "rows":recs + } + print(len(recs),'records return') + await self.freeSqlor(sor) + return data + return wrap_func + + async def runSQLResultFields(self, dbname,NS,**kwargs): + sor = self.getSqlor(dbname) + desc = func(dbname,NS,**kw) + conn = await self._aquireConn(dbname) + async with conn.cursor() as cur: + sor.setCursor(conn,cur) + ret=await sor.sqlIterator(desc,NS) + ret = [ {'name':i[0],'type':i[1]} for i in cur.description ] + return ret + await self._releaseConn(dbname,conn) + + async def getTables(self,dbname): + @self.inSqlor + async def _getTables(sor,dbname): + ret = await sor.tables() + return ret + return await _getTables(None,dbname) + + async def getTableFields(self,dbname,tblname): + @self.inSqlor + async def _getTableFields(sor,dbname,tblname): + ret = await sor.fields(tblname) + return ret + return await _getTableFields(None,dbname,tblname) + + async def getTablePrimaryKey(self,dbname,tblname): + @self.inSqlor + async def _getTablePrimaryKey(sor,dbname,tblname): + ret = await sor.primary(tblname) + return ret + return await _getTablePrimaryKey(None,dbname,tblname) + + async def getTableForignKeys(self,dbname,tblname): + @self.inSqlor + async def _getTableForignKeys(sor,dbname,tblname): + ret = await sor.fkeys(tblname) + return ret + return await _getTableForignKeys(None,dbname,tblname) + diff --git a/build/lib/sqlor/ddl_template_mysql.py b/build/lib/sqlor/ddl_template_mysql.py new file mode 100644 index 0000000..5c9b039 --- /dev/null +++ b/build/lib/sqlor/ddl_template_mysql.py @@ -0,0 +1,55 @@ +mysql_ddl_tmpl = """{% macro typeStr(type,len,dec) %} +{%- if type=='str' -%} +VARCHAR({{len}}) +{%- elif type=='char' -%} +CHAR({{len}}) +{%- elif type=='long' or type=='int' or type=='short' -%} +int +{%- elif type=='long' -%} +bigint +{%- elif type=='float' or type=='double' or type=='ddouble' -%} +double({{len}},{{dec}}) +{%- elif type=='date' -%} +date +{%- elif type=='time' -%} +time +{%- elif type=='datetime' -%} +datetime +{%- elif type=='timestamp' -%} +TIMESTAMP +{%- elif type=='text' -%} +longtext +{%- elif type=='bin' -%} +longblob +{%- else -%} +{{type}} +{%- endif %} +{%- endmacro %} +{% macro nullStr(nullable) %} +{%- if nullable=='no' -%} +NOT NULL +{%- endif -%} +{% endmacro %} +{% macro primary() %} +{% if len(','.join(summary[0].primary))>0 %} +,primary key({{','.join(summary[0].primary)}}) +{% endif %} +{% endmacro %} +drop table {{summary[0].name}}; +CREATE TABLE {{summary[0].name}} +( +{% for field in fields %} + {{field.name}} {{typeStr(field.type,field.length,field.dec)}} {{nullStr(field.nullable)}} {%if field.title -%} comment '{{field.title}}'{%- endif %}{%- if not loop.last -%},{%- endif -%} +{% endfor %} +{{primary()}} +) +engine=innodb +default charset=utf8 +{% if summary[0].title %}comment '{{summary[0].title}}'{% endif %} +; +{% for v in validation %} +{% if v.oper=='idx' %} +CREATE {% if v.value.idxtype=='unique' %}UNIQUE{% endif %} INDEX {{summary[0].name}}_{{v.name}} ON {{summary[0].name}}({{",".join(v.value.fields)}}); +{% endif %} +{%- endfor -%} +""" \ No newline at end of file diff --git a/build/lib/sqlor/ddl_template_oracle.py b/build/lib/sqlor/ddl_template_oracle.py new file mode 100644 index 0000000..ff39372 --- /dev/null +++ b/build/lib/sqlor/ddl_template_oracle.py @@ -0,0 +1,49 @@ +oracle_ddl_tmpl = """{% macro typeStr(type,len,dec) %} +{%- if type=='str' -%} +VARCHAR2({{len}}) +{%- elif type=='char' -%} +CHAR({{len}}) +{%- elif type=='long' or type=='int' or type=='short' -%} +NUMBER +{%- elif type=='float' or type=='double' or type=='ddouble' -%} +NUMBER({{len}},{{dec}}) +{%- elif type=='date' or type=='time' -%} +DATE +{%- elif type=='timestamp' -%} +TIMESTAMP +{%- elif type=='text' -%} +CLOB +{%- elif type=='bin' -%} +BLOB +{%- else -%} +{{type}} +{%- endif %} +{%- endmacro %} +{% macro nullStr(nullable) %} +{%- if nullable=='no' -%} +NOT NULL +{%- endif -%} +{% endmacro %} +{% macro primary() %} +{% if len(','.join(summary[0].primary))>0 %} +,primary key({{','.join(summary[0].primary)}}) +{% endif %} +{% endmacro %} +drop table {{summary[0].name}}; +CREATE TABLE {{summary[0].name}} +( +{% for field in fields %} + {{field.name}} {{typeStr(field.type,field.length,field.dec)}} {{nullStr(field.nullable)}}{%- if not loop.last -%},{%- endif -%} +{% endfor %} +{{primary()}} +); +{% for v in validation %} +{% if v.oper=='idx' %} +CREATE {% if v.value.idxtype=='unique' %}UNIQUE{% endif %} INDEX {{summary[0].name}}_{{v.name}} ON {{summary[0].name}}({{",".join(v.value.fields)}}); +{% endif %} +{%- endfor -%} +COMMENT ON TABLE {{summary[0].name}} IS '{{summary[0].title}}'; +{% for field in fields %} +COMMENT ON COLUMN {{summary[0].name}}.{{field.name}} is '{{field.title}}'; +{% endfor %} +""" \ No newline at end of file diff --git a/build/lib/sqlor/ddl_template_postgresql.py b/build/lib/sqlor/ddl_template_postgresql.py new file mode 100644 index 0000000..6b17592 --- /dev/null +++ b/build/lib/sqlor/ddl_template_postgresql.py @@ -0,0 +1,45 @@ +postgresql_ddl_tmpl = """{% macro typeStr(type,len,dec) %} +{%- if type=='str' -%} +VARCHAR({{len}}) +{%- elif type=='char' -%} +CHAR({{len}}) +{%- elif type=='long' or type=='int' or type=='short' -%} +INTEGER +{%- elif type=='float' or type=='double' or type=='ddouble' -%} +NUMERIC({{len}},{{dec}}) +{%- elif type=='date' -%} +DATE +{%- elif type=='time' -%} +TIME +{%- elif type=='timestamp' -%} +TIMESTAMP +{%- else -%} +{{type}} +{%- endif %} +{%- endmacro %} +{% macro nullStr(nullable) %} +{%- if nullable=='no' -%} +NOT NULL +{%- endif -%} +{% endmacro %} +{% macro primary() %} +primary key({{','.join(summary[0].primary)}}) +{% endmacro %} +DROP TABLE IF EXISTS {{summary[0].name}}; +CREATE TABLE {{summary[0].name}} +( +{% for field in fields %} + {{field.name}} {{typeStr(field.type,field.length,field.dec)}} {{nullStr(field.nullable)}}, +{% endfor %} +{{primary()}} +); +{% for v in validation %} +{% if v.oper=='idx' %} +CREATE {% if v.value.idxtype=='unique' %}UNIQUE{% endif %} INDEX {{summary[0].name}}_{{v.name}} ON {{summary[0].name}}({{",".join(v.value.fields)}}); +{% endif %} +{%- endfor -%} +COMMENT ON TABLE {{summary[0].name}} IS '{{summary[0].title.decode('utf8')}}'; +{% for field in fields %} +COMMENT ON COLUMN {{summary[0].name}}.{{field.name}} is '{{field.title.decode('utf8')}}'; +{% endfor %} +""" \ No newline at end of file diff --git a/build/lib/sqlor/ddl_template_sqlserver.py b/build/lib/sqlor/ddl_template_sqlserver.py new file mode 100644 index 0000000..49e73c7 --- /dev/null +++ b/build/lib/sqlor/ddl_template_sqlserver.py @@ -0,0 +1,51 @@ +sqlserver_ddl_tmpl = """{% macro typeStr(type,len,dec) %} +{%- if type=='str' -%} +NVARCHAR({{len}}) +{%- elif type=='char' -%} +CHAR({{len}}) +{%- elif type=='long' or type=='int' or type=='short' -%} +NUMERIC +{%- elif type=='float' or type=='double' or type=='ddouble' -%} +numeric({{len}},{{dec}}) +{%- elif type=='date' or type=='time' -%} +DATE +{%- elif type=='timestamp' -%} +TIMESTAMP +{%- elif type=='text' -%} +NVARCHAR(MAX) +{%- elif type=='bin' -%} +IMAGE +{%- else -%} +{{type}} +{%- endif %} +{%- endmacro %} +{% macro nullStr(nullable) %} +{%- if nullable=='no' -%} +NOT NULL +{%- endif -%} +{% endmacro %} + +{% macro primary() %} +{% if len(','.join(summary[0].primary))>0 %} +,primary key({{','.join(summary[0].primary)}}) +{% endif %} +{% endmacro %} + +drop table dbo.{{summary[0].name}}; +CREATE TABLE dbo.{{summary[0].name}} +( +{% for field in fields %} + {{field.name}} {{typeStr(field.type,field.length,field.dec)}} {{nullStr(field.nullable)}}{%- if not loop.last -%},{%- endif -%} +{% endfor %} +{{primary()}} +) +{% for v in validation %} +{% if v.oper=='idx' %} +CREATE {% if v.value.idxtype=='unique' %}UNIQUE{% endif %} INDEX {{summary[0].name}}_{{v.name}} ON {{summary[0].name}}({{",".join(v.value.fields)}}); +{% endif %} +{%- endfor -%} +EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'{{summary[0].title}}' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'{{summary[0].name}}' +{% for field in fields %} +EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'{{field.title}}' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'{{summary[0].name}}', @level2type=N'COLUMN',@level2name=N'{{field.name}}' +{% endfor %} +""" \ No newline at end of file diff --git a/build/lib/sqlor/mssqlor.py b/build/lib/sqlor/mssqlor.py new file mode 100644 index 0000000..b527a1a --- /dev/null +++ b/build/lib/sqlor/mssqlor.py @@ -0,0 +1,175 @@ +# -*- coding:utf8 -*- +from .sor import SQLor +from .ddl_template_sqlserver import sqlserver_ddl_tmpl + +class MsSqlor(SQLor): + ddl_template = sqlserver_ddl_tmpl + db2modelTypeMapping = { + 'bit':'short', + 'tinyint':'short', + 'date':'date', + 'bigint':'long', + 'smallint':'short', + 'int':'long', + 'decimal':'float', + 'numeric':'float', + 'smallmoney':'float', + 'money':'float', + 'real':'float', + 'float':'float', + 'datetime':'date', + 'timestamp':'timestamp', + 'uniqueidentifier':'timestamp', + 'char':'char', + 'varchar':'str', + 'text':'text', + 'nchar':'str', + 'nvarchar':'str', + 'ntext':'text', + 'binary':'str', + 'varbinary':'str', + 'image':'file', + } + model2dbTypemapping = { + 'date':'datetime', + 'time':'date', + 'timestamp':'timestamp', + 'str':'nvarchar', + 'char':'char', + 'short':'int', + 'long':'numeric', + 'float':'numeric', + 'text':'ntext', + 'file':'image', + } + @classmethod + def isMe(self,name): + return name=='pymssql' + + def grammar(self): + return { + 'select':select_stmt, + } + + def placeHolder(self,varname): + if varname=='__mainsql__' : + return '' + return '%s' + + def dataConvert(self,dataList): + if type(dataList) == type({}): + d = [ i for i in dataList.values()] + else: + d = [ i['value'] for i in dataList] + return tuple(d) + + def pagingSQLmodel(self): + return u"""select * +from ( + select row_number() over(order by $[sort]$ $[order]$) as _row_id,page_s.* + from (%s) page_s + ) A +where _row_id >= $[from_line]$ and _row_id < $[end_line]$""" + + def tablesSQL(self): + sqlcmd = u"""select + lower(d.name) as name, + lower(cast(Isnull(f.VALUE,d.name) as nvarchar )) title + from sysobjects d + left join sys.extended_properties f on d.id = f.major_id and f.minor_id = 0 + where d.xtype = 'U'""" + return sqlcmd + + def fieldsSQL(self,tablename=None): + sqlcmd=u"""SELECT name = lower(a.name) + ,type = b.name + ,length = Columnproperty(a.id,a.name,'PRECISION') + ,dec = Isnull(Columnproperty(a.id,a.name,'Scale'),null) + ,nullable = CASE + WHEN a.isnullable = 1 THEN 'yes' + ELSE 'no' + END + ,title = lower(cast(Isnull(g.[value],a.name) as nvarchar) ) + ,table_name = lower(d.name) + FROM syscolumns a + LEFT JOIN systypes b + ON a.xusertype = b.xusertype + INNER JOIN sysobjects d + ON (a.id = d.id) + AND (d.xtype = 'U') + AND (d.name <> 'dtproperties') + INNER JOIN sys.all_objects c + ON d.id=c.object_id + AND schema_name(schema_id)='dbo' + LEFT JOIN sys.extended_properties g + ON (a.id = g.major_id) + AND (a.colid = g.minor_id) + LEFT JOIN sys.extended_properties f + ON (d.id = f.major_id) + AND (f.minor_id = 0)""" + if tablename is not None: + sqlcmd = sqlcmd + """ where lower(d.name)='%s' + ORDER BY a.id,a.colorder""" % tablename.lower() + else: + sqlcmd = sqlcmd + """ ORDER BY a.id,a.colorder""" + return sqlcmd + + def fkSQL(self,tablename=None): + sqlcmd = u"""select + MainCol.name AS field -- [主表列名] + ,oSub.name AS fk_table -- [子表名称], + ,SubCol.name AS fk_field -- [子表列名], +from + sys.foreign_keys fk + JOIN sys.all_objects oSub + ON (fk.parent_object_id = oSub.object_id) + JOIN sys.all_objects oMain + ON (fk.referenced_object_id = oMain.object_id) + JOIN sys.foreign_key_columns fkCols + ON (fk.object_id = fkCols.constraint_object_id) + JOIN sys.columns SubCol + ON (oSub.object_id = SubCol.object_id + AND fkCols.parent_column_id = SubCol.column_id) + JOIN sys.columns MainCol + ON (oMain.object_id = MainCol.object_id + AND fkCols.referenced_column_id = MainCol.column_id)""" + if tablename is not None: + sqlcmd = sqlcmd + """ where lower(oMain.name) = '%s'""" % tablename.lower() + + return sqlcmd + + def pkSQL(self,tablename=None): + sqlcmd = u"""select + lower(a.table_name) as table_name, + lower(b.column_name) as field_name + from information_schema.table_constraints a + inner join information_schema.constraint_column_usage b + on a.constraint_name = b.constraint_name + where a.constraint_type = 'PRIMARY KEY'""" + if tablename is not None: + sqlcmd = sqlcmd + """ and lower(a.table_name) = '%s'""" % tablename.lower() + return sqlcmd + + def indexesSQL(self,tablename=None): + sqlcmd = """SELECT +index_name=lower(IDX.Name), +index_type=case when KC.type_desc is null then 'primary' WHEN IDX.is_unique=1 THEN 'unique' ELSE 'ununique' END, +table_name=lower(O.Name), +column_name=lower(C.Name) +FROM sys.indexes IDX +INNER JOIN sys.index_columns IDXC +ON IDX.[object_id]=IDXC.[object_id] +AND IDX.index_id=IDXC.index_id +LEFT JOIN sys.key_constraints KC +ON IDX.[object_id]=KC.[parent_object_id] +AND IDX.index_id=KC.unique_index_id +INNER JOIN sys.objects O +ON O.[object_id]=IDX.[object_id] +INNER JOIN sys.columns C +ON O.[object_id]=C.[object_id] +AND O.type='U' +AND O.is_ms_shipped=0 +AND IDXC.Column_id=C.Column_id""" + if tablename is not None: + sqlcmd = sqlcmd + """ where lower(O.name)='%s'""" % tablename.lower() + return sqlcmd diff --git a/build/lib/sqlor/mysqlor.py b/build/lib/sqlor/mysqlor.py new file mode 100644 index 0000000..e5df373 --- /dev/null +++ b/build/lib/sqlor/mysqlor.py @@ -0,0 +1,179 @@ +# -*- coding:utf8 -*- +from mysql import connector +from appPublic.argsConvert import ArgsConvert,ConditionConvert + +from .sor import SQLor +from .ddl_template_mysql import mysql_ddl_tmpl +class MySqlor(SQLor): + ddl_template = mysql_ddl_tmpl + db2modelTypeMapping = { + 'tinyint':'short', + 'smallint':'short', + 'mediumint':'long', + 'int':'long', + 'bigint':'long', + 'decimal':'float', + 'double':'float', + 'float':'float', + 'char':'char', + 'varchar':'str', + 'tinyblob':'text', + 'tinytext':'text', + 'mediumblob':'text', + 'mediumtext':'text', + 'blob':'text', + 'text':'text', + 'mediumblob':'text', + 'mediumtext':'text', + 'longblob':'bin', + 'longtext':'text', + 'barbinary':'text', + 'binary':'text', + 'date':'date', + 'time':'time', + 'datetime':'datetime', + 'timestamp':'datestamp', + 'year':'short', + } + model2dbTypemapping = { + 'date':'date', + 'time':'date', + 'timestamp':'timestamp', + 'str':'varchar', + 'char':'char', + 'short':'int', + 'long':'bigint', + 'float':'double', + 'text':'longtext', + 'bin':'longblob', + 'file':'longblob', + } + @classmethod + def isMe(self,name): + return name=='mysql.connector' + + def grammar(self): + return { + 'select':select_stmt, + } + + def _opendb(self): + self.conn = connector.connect(**self.dbdesc['kwargs']) + + def placeHolder(self,varname): + if varname=='__mainsql__' : + return '' + return '%s' + + def dataConvert(self,dataList): + if type(dataList) == type({}): + d = [ i for i in dataList.values()] + else: + d = [ i['value'] for i in dataList] + return tuple(d) + + def pagingSQL(self,sql,paging,NS): + """ + default it not support paging + """ + page = int(NS.get(paging['pagename'],1)) + rows = int(NS.get(paging['rowsname'],10)) + sort = NS.get(paging.get('sortname','sort'),None) + order = NS.get(paging.get('ordername','asc'),'asc') + if not sort: + return sql + if page < 1: + page = 1 + from_line = (page - 1) * rows + end_line = page * rows + 1 + psql = self.pagingSQLmodel() + ns={ + 'from_line':from_line, + 'end_line':end_line, + 'rows':rows, + 'sort':sort, + 'order':order, + } + ac = ArgsConvert('$[',']$') + psql = ac.convert(psql,ns) + retSQL=psql % sql + return retSQL + + def pagingSQLmodel(self): + return u"""select * from (%s) A order by $[sort]$ $[order]$ +limit $[from_line]$,$[rows]$""" + + def tablesSQL(self): + sqlcmd = """SELECT lower(TABLE_NAME) as name, lower(TABLE_COMMENT) as title FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '%s'""" % self.dbdesc.get('dbname','unknown') + return sqlcmd + + def fieldsSQL(self,tablename=None): + sqlcmd=""" + select + lower(column_name) as name, + data_type as type, + case when character_maximum_length is null then NUMERIC_PRECISION + else character_maximum_length end + as length, + NUMERIC_SCALE as dec1, + lower(is_nullable) as nullable, + column_comment as title, + lower(table_name) as table_name + from information_schema.columns where lower(TABLE_SCHEMA) = '%s' """ % self.dbdesc.get('dbname','unknown').lower() + if tablename is not None: + sqlcmd = sqlcmd + """and lower(table_name)='%s';""" % tablename.lower() + return sqlcmd + + def fkSQL(self,tablename=None): + sqlcmd = """SELECT C.TABLE_SCHEMA 拥有者, + C.REFERENCED_TABLE_NAME 父表名称 , + C.REFERENCED_COLUMN_NAME 父表字段 , + C.TABLE_NAME 子表名称, + C.COLUMN_NAME 子表字段, + C.CONSTRAINT_NAME 约束名, + T.TABLE_COMMENT 表注释, + R.UPDATE_RULE 约束更新规则, + R.DELETE_RULE 约束删除规则 + FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE C + JOIN INFORMATION_SCHEMA. TABLES T + ON T.TABLE_NAME = C.TABLE_NAME + JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS R + ON R.TABLE_NAME = C.TABLE_NAME + AND R.CONSTRAINT_NAME = C.CONSTRAINT_NAME + AND R.REFERENCED_TABLE_NAME = C.REFERENCED_TABLE_NAME + WHERE C.REFERENCED_TABLE_NAME IS NOT NULL ; + and C.TABLE_SCHEMA = '%s' +""" % self.dbdesc.get('dbname','unknown').lower() + if tablename is not None: + sqlcmd = sqlcmd + " and C.REFERENCED_TABLE_NAME = '%s'" % tablename.lower() + return sqlcmd + + def pkSQL(self,tablename=None): + sqlcmd = """SELECT + lower(c.table_name) as table_name, + lower(c.COLUMN_NAME) as field_name +FROM + INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS t, + INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS c +WHERE + t.CONSTRAINT_TYPE = 'PRIMARY KEY' + AND t.TABLE_SCHEMA = '%s' + AND t.TABLE_NAME = c.TABLE_NAME +""" % self.dbdesc.get('dbname','unknown').lower() + if tablename is not None: + sqlcmd = sqlcmd + " AND c.TABLE_NAME = '%s'" % tablename.lower() + return sqlcmd + + def indexesSQL(self,tablename=None): + sqlcmd = """SELECT DISTINCT + lower(index_name) as index_name, + lower(index_type) as index_type, + lower(table_name) as table_name, + lower(column_name) as column_name +FROM + information_schema.statistics +WHERE + table_schema = '%s'""" % self.dbdesc.get('dbname','unknown') + if tablename is not None: + sqlcmd = sqlcmd + """ AND table_name = '%s'""" % tablename.lower() + return sqlcmd diff --git a/build/lib/sqlor/oracleor.py b/build/lib/sqlor/oracleor.py new file mode 100644 index 0000000..75ca325 --- /dev/null +++ b/build/lib/sqlor/oracleor.py @@ -0,0 +1,131 @@ +from .sor import SQLor +from .ddl_template_oracle import oracle_ddl_tmpl +class Oracleor(SQLor): + ddl_template = oracle_ddl_tmpl + db2modelTypeMapping = { + 'char':'char', + 'nchar':'str', + 'varchar':'str', + 'varchar2':'str', + 'nvarchar2':'str', + 'number':'long', + 'integer':'long', + 'binary_float':'float', + 'binary_double':'float', + 'float':'float', + 'timestamp':'timestamp', + 'timestamp with time zone':'timestamp', + 'timestamp with local time zone':'timestamp', + 'interval year to moth':'date', + 'interval day to second':'timestamp', + 'clob':'text', + 'nclob':'text', + 'blob':'file', + 'bfile':'file', + 'date':'date', + } + model2dbTypemapping = { + 'date':'date', + 'time':'date', + 'timestamp':'date', + 'str':'varchar2', + 'char':'char', + 'short':'number', + 'long':'number', + 'float':'number', + 'text':'nclob', + 'file':'blob', + } + @classmethod + def isMe(self,name): + return name=='cx_Oracle' + + def grammar(self): + return { + 'select':select_stmt, + } + + def placeHolder(self,varname): + if varname=='__mainsql__' : + return '' + return ':%s' % varname + + def dataConvert(self,dataList): + if type(dataList) == type({}): + return dataList + d = {} + [ d.update({i['name']:i['value']}) for i in dataList ] + return d + + def pagingSQLmodel(self): + return u"""select * +from ( + select page_s.*,rownum row_id + from (%s) page_s + order by $[sort]$ $[order]$ + ) +where row_id >=$[from_line]$ and row_id < $[end_line]$""" + + def tablesSQL(self): + sqlcmd = """select +lower(table_name) as name, +lower(decode(comments,null,table_name,comments)) as title +from USER_TAB_COMMENTS where table_type = 'TABLE'""" + return sqlcmd + + def fieldsSQL(self,tablename=None): + sqlcmd="""select lower(utc.COLUMN_NAME) name + ,utc.DATA_TYPE type + ,utc.DATA_LENGTH length + ,utc.data_scale dec + ,case when utc.nullable = 'Y' then 'yes' else 'no' end nullable + ,lower(nvl(ucc.comments,utc.COLUMN_NAME)) title + ,lower(utc.table_name) as table_name + from user_tab_cols utc left join USER_COL_COMMENTS ucc on utc.table_name = ucc.table_name and utc.COLUMN_NAME = ucc.COLUMN_NAME""" + if tablename is not None: + sqlcmd = sqlcmd + """ where lower(utc.table_name) = '%s'""" % tablename.lower() + return sqlcmd + + def fkSQL(self,tablename=None): + tablename = tablename.lower() + sqlcmd = """select + distinct(ucc.column_name) as field,rela.table_name as fk_table,rela.column_name as fk_field +from + user_constraints uc,user_cons_columns ucc, + ( + select t2.table_name,t2.column_name,t1.r_constraint_name + from user_constraints t1,user_cons_columns t2 + where t1.r_constraint_name=t2.constraint_name +) rela +where + uc.constraint_name=ucc.constraint_name + and uc.r_constraint_name=rela.r_constraint_name""" + if tablename is not None: + sqlcmd = sqlcmd + """ and lower(uc.table_name)='%s'""" % tablename.lower() + return sqlcmd + + def pkSQL(self,tablename=None): + sqlcmd = """ +select + lower(col.table_name) table_name, + lower(col.column_name) as field_name +from + user_constraints con,user_cons_columns col +where + con.constraint_name=col.constraint_name and con.constraint_type='P'""" + if tablename is not None: + sqlcmd = sqlcmd + """ and lower(col.table_name)='%s'""" % tablename.lower() + return sqlcmd + + def indexesSQL(self,tablename=None): + sqlcmd = """select + lower(a.index_name) index_name, + lower(a.UNIQUENESS) index_type, + lower(a.table_name) table_name, + lower(b.column_name) column_name +from user_indexes a, user_ind_columns b +where a.index_name = b.index_name""" + if tablename is not None: + sqlcmd += """ and lower(a.table_name) = lower('%s')""" % tablename.lower() + return sqlcmd + diff --git a/build/lib/sqlor/postgresqlor.py b/build/lib/sqlor/postgresqlor.py new file mode 100644 index 0000000..0fd3033 --- /dev/null +++ b/build/lib/sqlor/postgresqlor.py @@ -0,0 +1,199 @@ +from .sor import SQLor +from .ddl_template_postgresql import postgresql_ddl_tmpl + +class PostgreSQLor(SQLor): + ddl_template = postgresql_ddl_tmpl + db2modelTypeMapping = { + 'smallint':'short', + 'integer':'long', + 'bigint':'llong', + 'decimal':'float', + 'numeric':'float', + 'real':'float', + 'double':'float', + 'serial':'long', + 'bigserial':'llong', + 'char':'char', + 'character':'char', + 'varchar':'str', + 'character varying':'str', + 'text':'text', + 'timestamp':'timestamp', + 'date':'date', + 'time':'time', + 'boolean':'char', + 'bytea':'file' + } + model2dbTypemapping = { + 'date':'date', + 'time':'date', + 'timestamp':'timestamp', + 'str':'varchar', + 'char':'char', + 'short':'smallint', + 'long':'integer', + 'float':'numeric', + 'text':'text', + 'file':'bytea', + } + @classmethod + def isMe(self,name): + return name=='psycopg2' + + def grammar(self): + return { + 'select':select_stmt, + } + + def placeHolder(self,varname): + if varname=='__mainsql__' : + return '' + return ':%s' % varname + + def dataConvert(self,dataList): + if type(dataList) == type({}): + return dataList + d = {} + [ d.update({i['name']:i['value']}) for i in dataList ] + return d + + def pagingSQLmodel(self): + return u"""select * +from ( + select page_s.*,rownum row_id + from (%s) page_s + order by $[sort]$ $[order]$ + ) +where row_id >=$[from_line]$ and row_id < $[end_line]$""" + + def tablesSQL(self): + """ + 列出表名 + SELECT tablename FROM pg_tables; + WHERE tablename NOT LIKE 'pg%' + AND tablename NOT LIKE 'sql_%' + ORDER BY tablename; + """ + sqlcmd = """select +lower(table_name) as name, +lower(decode(comments,null,table_name,comments)) as title +from USER_TAB_COMMENTS where table_type = 'TABLE'""" + return sqlcmd + + def fieldsSQL(self,tablename=None): + """SELECT col_description(a.attrelid,a.attnum) as comment,pg_type.typname as typename,a.attname as name, a.attnotnull as notnull +FROM pg_class as c,pg_attribute as a inner join pg_type on pg_type.oid = a.atttypid +where c.relname = 'tablename' and a.attrelid = c.oid and a.attnum>0 + """ + sqlcmd="""select lower(utc.COLUMN_NAME) name + ,utc.DATA_TYPE type + ,utc.DATA_LENGTH length + ,utc.data_scale dec + ,case when utc.nullable = 'Y' then 'yes' else 'no' end nullable + ,lower(nvl(ucc.comments,utc.COLUMN_NAME)) title + ,lower(utc.table_name) as table_name + from user_tab_cols utc left join USER_COL_COMMENTS ucc on utc.table_name = ucc.table_name and utc.COLUMN_NAME = ucc.COLUMN_NAME""" + if tablename is not None: + sqlcmd = sqlcmd + """ where lower(utc.table_name) = '%s'""" % tablename.lower() + return sqlcmd + + def fkSQL(self,tablename=None): + tablename = tablename.lower() + sqlcmd = """select + distinct(ucc.column_name) as field,rela.table_name as fk_table,rela.column_name as fk_field +from + user_constraints uc,user_cons_columns ucc, + ( + select t2.table_name,t2.column_name,t1.r_constraint_name + from user_constraints t1,user_cons_columns t2 + where t1.r_constraint_name=t2.constraint_name +) rela +where + uc.constraint_name=ucc.constraint_name + and uc.r_constraint_name=rela.r_constraint_name""" + if tablename is not None: + sqlcmd = sqlcmd + """ and lower(uc.table_name)='%s'""" % tablename.lower() + return sqlcmd + + def pkSQL(self,tablename=None): + """ + select pg_attribute.attname as colname,pg_type.typname as typename,pg_constraint.conname as pk_name from +pg_constraint inner join pg_class +on pg_constraint.conrelid = pg_class.oid +inner join pg_attribute on pg_attribute.attrelid = pg_class.oid +and pg_attribute.attnum = pg_constraint.conkey[1] +inner join pg_type on pg_type.oid = pg_attribute.atttypid +where pg_class.relname = 'tablename' +and pg_constraint.contype='p' + """ + sqlcmd = """ +select + lower(col.table_name) table_name, + lower(col.column_name) as field_name +from + user_constraints con,user_cons_columns col +where + con.constraint_name=col.constraint_name and con.constraint_type='P'""" + if tablename is not None: + sqlcmd = sqlcmd + """ and lower(col.table_name)='%s'""" % tablename.lower() + return sqlcmd + + def indexesSQL(self,tablename=None): + """ +SELECT + +A.SCHEMANAME, + +A.TABLENAME, + +A.INDEXNAME, + +A.TABLESPACE, + +A.INDEXDEF, + +B.AMNAME, + +C.INDEXRELID, + +C.INDNATTS, + +C.INDISUNIQUE, + +C.INDISPRIMARY, + +C.INDISCLUSTERED, + +D.DESCRIPTION + +FROM + +PG_AM B + +LEFT JOIN PG_CLASS F ON B.OID = F.RELAM + +LEFT JOIN PG_STAT_ALL_INDEXES E ON F.OID = E.INDEXRELID + +LEFT JOIN PG_INDEX C ON E.INDEXRELID = C.INDEXRELID + +LEFT OUTER JOIN PG_DESCRIPTION D ON C.INDEXRELID = D.OBJOID, + +PG_INDEXES A + +WHERE + +A.SCHEMANAME = E.SCHEMANAME AND A.TABLENAME = E.RELNAME AND A.INDEXNAME = E.INDEXRELNAME + +AND E.SCHEMANAME = 'public' AND E.RELNAME = 'table_name' +""" + sqlcmd = """select + lower(a.index_name) index_name, + lower(a.UNIQUENESS) index_type, + lower(a.table_name) table_name, + lower(b.column_name) column_name +from user_indexes a, user_ind_columns b +where a.index_name = b.index_name""" + if tablename is not None: + sqlcmd += """ and lower(a.table_name) = lower('%s')""" % tablename.lower() + return sqlcmd + diff --git a/build/lib/sqlor/sor.py b/build/lib/sqlor/sor.py new file mode 100644 index 0000000..b3a18b4 --- /dev/null +++ b/build/lib/sqlor/sor.py @@ -0,0 +1,454 @@ +import os +os.environ['NLS_LANG'] = 'SIMPLIFIED CHINESE_CHINA.UTF8' +import sys +import codecs +import re +import json +from appPublic.myImport import myImport +from appPublic.dictObject import DictObject,dictObjectFactory +from appPublic.unicoding import uDict +from patterncoding.myTemplateEngine import MyTemplateEngine + + +from appPublic.argsConvert import ArgsConvert,ConditionConvert + +class SQLorException(Exception,object): + def __int__(self,**kvs): + supper(SQLException,self).__init__(self,**kvs) + self.dic = { + 'response':'error', + 'errtype':'SQLor', + 'errmsg':supper(SQLException,self).message, + } + + def __str__(self): + return 'errtype:%s,errmsg=%s' % (self.dic['errtype'],self.dic['errmsg']) + +def setValues(params,ns): + r = ns.get(params,os.getenv(params)) + return r + +def findNamedParameters(sql): + """ + return a list of named parameters + """ + re1 = '\$\{[_a-zA-Z_][a-zA-Z_0-9]*\}' + params1 = re.findall(re1,sql) + return params1 + + +def uniParams(params1): + ret = [] + for i in params1: + if i not in ret: + ret.append(i) + return ret + +def readsql(fn): + f = codecs.open(fn,'r','utf-8') + b = f.read() + f.close() + return b + +class SQLor(object): + def __init__(self,dbdesc=None,sqltp = '$[',sqlts = ']$',sqlvp = '${',sqlvs = '}$'): + self.conn = None + self.cur = None + self.async_mode = False + self.sqltp = sqltp + self.sqlts = sqlts + self.sqlvp = sqlvp + self.sqlvs = sqlvs + self.dbdesc = dbdesc + self.writer = None + self.convfuncs = {} + self.cc = ConditionConvert() + + def setCursor(self,async_mode,conn,cur): + self.async_mode = async_mode + self.conn = conn + self.cur = cur + + def getConn(self): + return self.conn + + def setConvertFunction(self,typ,func): + self.convfuncs.update({typ:func}) + + def convert(self,typ,value): + if self.convfuncs.get(typ,None) is not None: + return self.convfuncs[typ](value) + return value + @classmethod + def isMe(self,name): + return name=='sqlor' + + def pagingSQLmodel(self): + return u"" + + def placeHolder(self,varname): + if varname=='__mainsql__' : + return '' + return '?' + + def dataConvert(self,dataList): + return [ i.get('value',None) for i in dataList] + + def dataList(self,k,v): + a = [] + a.append({'name':k,'value':v}) + return a + + def cursor(self): + return self.cur + + def recordCnt(self,sql): + ret = u"""select count(*) rcnt from (%s) rowcount_table""" % sql + return ret + + def pagingSQL(self,sql,paging,NS): + """ + default it not support paging + """ + page = int(NS.get(paging['pagename'],1)) + rows = int(NS.get(paging['rowsname'],10)) + sort = NS.get(paging.get('sortname','sort'),None) + order = NS.get(paging.get('ordername','asc'),'asc') + if not sort: + return sql + if page < 1: + page = 1 + from_line = (page - 1) * rows + 1 + end_line = page * rows + 1 + psql = self.pagingSQLmodel() + ns={ + 'from_line':from_line, + 'end_line':end_line, + 'rows':rows, + 'sort':sort, + 'order':order, + } + ac = ArgsConvert('$[',']$') + psql = ac.convert(psql,ns) + retSQL=psql % sql + return retSQL + + def filterSQL(self,sql,filters,NS): + ac = ArgsConvert('$[',']$') + fbs = [] + for f in filters: + vars = ac.findAllVariables(f) + if len(vars) > 0: + ignoreIt = False + for v in vars: + if not NS.get(v,False): + ignoreIt = True + if not ignoreIt: + f = ac.convert(f,NS) + else: + f = '1=1' + fbs.append(f) + fb = ' '.join(fbs) + retsql = u"""select * from (%s) filter_table where %s""" % (sql,fb) + return retsql + + async def runVarSQL(self,cursor,sql,NS): + """ + using a opened cursor to run a SQL statment with variable, the variable is setup in NS namespace + return a cursor with data + """ + markedSQL,datas = self.maskingSQL(sql,NS) + datas = self.dataConvert(datas) + try: + markedSQL = markedSQL.encode('utf8') + if self.async_mode: + await cursor.execute(markedSQL,datas) + else: + cursor.execute(markedSQL,datas) + + except Exception as e: + print( "markedSQL=",markedSQL,datas,e) + raise e + return + + def maskingSQL(self,org_sql,NS): + """ + replace all ${X}$ format variable exception named by '__mainsql__' in sql with '%s', + and return the marked sql sentent and variable list + sql is a sql statment with variable formated in '${X}$ + the '__mainsql__' variable use to identify the main sql will outout data. + NS is the name space the variable looking for, it is a variable dictionary + return (MarkedSQL,list_of_variable) + """ + sqltextAC = ArgsConvert(self.sqltp,self.sqlts) + sqlargsAC = ArgsConvert(self.sqlvp,self.sqlvs) + sql1 = sqltextAC.convert(org_sql,NS) + cc = ConditionConvert() + sql1 = cc.convert(sql1,NS) + vars = sqlargsAC.findAllVariables(sql1) + phnamespace = {} + [phnamespace.update({v:self.placeHolder(v)}) for v in vars] + m_sql = sqlargsAC.convert(sql1,phnamespace) + newdata = [] + for v in vars: + if v != '__mainsql__': + value = sqlargsAC.getVarValue(v,NS,None) + newdata += self.dataList(v,value) + + return (m_sql,newdata) + + async def execute(self,sql,value,callback,**kwargs): + cur = self.cursor() + await self.runVarSQL(cur,sql,value) + if callback is not None: + fields = [ i[0].lower() for i in cur.description ] + if self.async_mode: + rec = await cur.fetchone() + else: + rec = cur.fetchone() + + while rec is not None: + dic = {} + for i in range(len(fields)): + dic.update({fields[i]:rec[i]}) + #dic = uDict(dic,coding='utf8') + callback(dic,**kwargs) + if self.async_mode: + rec = await cur.fetchone() + else: + rec = cur.fetchone() + + + async def executemany(self,sql,values): + cur = self.cursor() + markedSQL,datas = self.maskingSQL(sql,{}) + datas = [ self.dataConvert(d) for d in values ] + if async_mode: + await cur.executemany(markedSQL,datas) + else: + cur.executemany(markedSQL,datas) + + def pivotSQL(self,tablename,rowFields,columnFields,valueFields): + def maxValue(columnFields,valueFields,cfvalues): + sql = '' + for f in valueFields: + i = 0 + for field in columnFields: + for v in cfvalues[field]: + sql += """ + ,sum(%s_%d) %s_%d""" % (f,i,f,i) + i+=1 + return sql + def casewhen(columnFields,valueFields,cfvalues): + sql = '' + for f in valueFields: + i = 0 + for field in columnFields: + for v in cfvalues[field]: + if v is None: + sql += """,case when %s is null then %s + else 0 end as %s_%d -- %s + """ % (field,f,f,i,v) + else: + sql += """,case when trim(%s) = trim('%s') then %s + else 0 end as %s_%d -- %s + """ % (field,v,f,f,i,v) + + i += 1 + return sql + + cfvalues={} + for field in columnFields: + sqlstring = 'select distinct %s from %s' % (field,tablename) + v = [] + self.execute(sqlstring,{},lambda x: v.append(x)) + cfvalues[field] = [ i[field] for i in v ] + + sql =""" + select """ + ','.join(rowFields) + sql += maxValue(columnFields,valueFields,cfvalues) + sql += """ from + (select """ + ','.join(rowFields) + sql += casewhen(columnFields,valueFields,cfvalues) + sql += """ + from %s) + group by %s""" % (tablename,','.join(rowFields)) + return sql + + async def pivot(self,desc,tablename,rowFields,columnFields,valueFields): + sql = self.pivotSQL(tablename,rowFields,columnFields,valueFields) + desc['sql_string'] = sql + ret = [] + return await self.execute(sql,{},lambda x:ret.append(x)) + + def isSelectSql(self,sql): + i = 0 + while sql[i] in "\r\n \t": + i = i + 1 + return sql.lower().startswith('select ') + + def getSQLfromDesc(self,desc): + sql = '' + if 'sql_file' in desc.keys(): + sql = readsql(desc['sql_file']) + else: + sql = desc['sql_string'] + return sql + + async def record_count(self,desc,NS): + cnt_desc = {} + cnt_desc.update(desc) + sql = self.getSQLfromDesc(desc) + if desc.get('sql_file',False): + del cnt_desc['sql_file'] + cnt_desc['sql_string'] = self.recordCnt(sql) + class Cnt: + def __init__(self): + self.recs = [] + def handler(self,rec): + self.recs.append(rec) + + c = Cnt() + await self.sqlIterator(cnt_desc,NS,c.handler) + print(c.recs[0]) + t = c.recs[0]['rcnt'] + return t + + async def pagingdata(self,desc,NS): + paging_desc = {} + paging_desc.update(desc) + paging_desc.update( + { + "paging":{ + "rowsname":"rows", + "pagename":"page", + "sortname":"sort", + "ordername":"order" + } + }) + if desc.get('sortfield',False): + NS['sort'] = desc.get('sortfield') + sql = self.getSQLfromDesc(desc) + if desc.get('sql_file',False): + del cnt_desc['sql_file'] + paging_desc['sql_string'] = self.pagingSQL(sql, + paging_desc.get('paging'),NS) + + class Cnt: + def __init__(self): + self.recs = [] + def handler(self,rec): + self.recs.append(rec) + + c = Cnt() + await self.sqlIterator(paging_desc,NS,c.handler) + return c.recs + + async def runSQL(self,desc,NS,callback,**kw): + class RecordHandler: + def __init__(self,ns,name): + self.ns = ns + self.name = name + self.ns[name] = [] + + def handler(self,rec): + obj = DictObject(rec) + self.ns[self.name].append(obj) + + cur = self.cursor() + sql = self.getSQLfromDesc(desc) + if self.isSelectSql(sql): + if callback is None: + klass = desc.get('dataname','dummy') + if klass is not None: + rh = RecordHandler(NS,klass) + callback = rh.handler + else: + callback = None + await self.execute(sql,NS,callback) + + async def sqlExecute(self,desc,NS): + await self.execute(sql,NS,None) + + async def tables(self): + sqlstring = self.tablesSQL() + ret = [] + await self.execute(sqlstring,{},lambda x:ret.append(x)) + return ret + + def indexesSQL(self,tablename): + """ + record of { + index_name, + index_type, + table_name, + column_name + } + """ + return None + + async def indexes(self,tablename=None): + sqlstring = self.indexesSQL(tablename.lower()) + if sqlstring is None: + return [] + recs = [] + await self.execute(sqlstring,{},lambda x:recs.append(x)) + return recs + + async def fields(self,tablename=None): + sqlstring = self.fieldsSQL(tablename) + recs = [] + await self.execute(sqlstring,{},lambda x:recs.append(x)) + ret = [] + for r in recs: + r.update({'type':self.db2modelTypeMapping.get(r['type'].lower(),'unknown')}) + r.update({'name':r['name'].lower()}) + ret.append(r) + return ret + + async def primary(self,tablename): + sqlstring = self.pkSQL(tablename) + recs = [] + await self.execute(sqlstring,{},lambda x:recs.append(x)) + return recs + + async def fkeys(self,tablename): + sqlstring = self.fkSQL(tablename) + recs = [] + await self.execute(sqlstring,{},lambda x:recs.append(x)) + return recs + + async def createTable(self,tabledesc): + te = MyTemplateEngine([],'utf8','utf8') + desc = { + "sql_string":te.renders(self.ddl_template,tabledesc) + } + return await self.sqlExecute(desc,{}) + + async def getTableDesc(self,tablename): + desc = {} + summary = [ i for i in await self.tables() if tablename.lower() == i.name ] + primary = [i.field_name for i in await self.primary(tablename) ] + summary['primary'] = primary + desc['summary'] = summary + desc['fields'] = await self.fields(tablename=tablename) + desc['validation'] = [] + idx = {} + async for idxrec in self.indexes(tablename=tablename): + if idxrec.index_name != idx.get('name',None): + if idx != {}: + desc['validation'].append(idx) + idx = { + 'fields':[] + } + else: + idx['fields'] = [] + idx['name'] = idxrec.index_name + idx['oper'] = 'idx' + idx['fields'].append(idxrec.field_name) + if idx != {}: + desc['validation'].append(idx) + return desc + + + diff --git a/build/lib/sqlor/sqlite3or.py b/build/lib/sqlor/sqlite3or.py new file mode 100644 index 0000000..adb065d --- /dev/null +++ b/build/lib/sqlor/sqlite3or.py @@ -0,0 +1,100 @@ +import re +from .sor import SQLor + +class SQLite3or(SQLor): + db2modelTypeMapping = { + 'char':'char', + 'nchar':'str', + 'text':'text', + 'ntext':'text', + 'varchar':'str', + 'nvarchar':'str', + 'blob':'file', + 'integer':'long', + 'double':'float', + 'date':'date', + 'time':'time', + 'timestamp':'timestamp', + 'number':'long', + } + model2dbTypemapping = { + 'date':'date', + 'time':'time', + 'timestamp':'timestamp', + 'str':'nvarchar', + 'char':'char', + 'short':'int', + 'long':'integer', + 'float':'double', + 'text':'ntext', + 'file':'blob', + } + @classmethod + def isMe(self,name): + return name=='sqlite3' + + def placeHolder(self,varname): + if varname=='__mainsql__' : + return '' + return '?' + + def dataConvert(self,dataList): + if type(dataList) == type({}): + d = [ i for i in dataList.values()] + else: + d = [ i['value'] for i in dataList] + return tuple(d) + + def pagingSQLmodel(self): + sql = u"""select * from (%s) order by $[sort]$ $[order]$ limit $[from_line]$,$[end_line]$""" + return sql + + def tablesSQL(self): + sqlcmd = u"""select name, tbl_name as title from sqlite_master where upper(type) = 'TABLE'""" + return sqlcmd + + def fieldsSQL(self,tablename): + sqlcmd="""PRAGMA table_info('%s')""" % tablename.lower() + return sqlcmd + + def fields(self,tablename): + m = u'(\w+)\(((\d+)(,(\d+)){0,1})\){0,1}' + k = re.compile(m) + def typesplit(typ): + d = k.search(typ) + if d is None: + return typ,0,0 + + return d.group(1),int(d.group(3) if d.group(3) is not None else 0 ),int(d.group(5) if d.group(5) is not None else 0) + + sqlstring = self.fieldsSQL(tablename) + recs = [] + self.execute(sqlstring,callback=lambda x:recs.append(x)) + for r in recs: + t,l,d = typesplit(r['type']) + r['type'] = t + r['length'] = int(l) + r['dec'] = int(d) + r['title'] = r['name'] + ret = [] + for r in recs: + r.update({'type':self.db2modelTypeMapping[r['type'].lower()]}) + r.update({'name':r['name'].lower()}) + ret.append(r) + return ret + + def fkSQL(self,tablename): + sqlcmd = "" + return sqlcmd + + def fkeys(self,tablename): + return [] + + def primary(self,tablename): + recs = self.fields(tablename) + ret = [ {'field':r['name']} for r in recs if r['pk'] == 1 ] + return ret + + def pkSQL(self,tablename): + sqlcmd = "" + return sqlcmd diff --git a/build/lib/sqlor/sqlor.py b/build/lib/sqlor/sqlor.py new file mode 100644 index 0000000..cbb03fc --- /dev/null +++ b/build/lib/sqlor/sqlor.py @@ -0,0 +1,393 @@ +import os +os.environ['NLS_LANG'] = 'SIMPLIFIED CHINESE_CHINA.UTF8' +import sys +import codecs +import re +import json +from appPublic.myImport import myImport +from appPublic.dictObject import DictObject,dictObjectFactory +from appPublic.unicoding import uDict +from patterncoding.myTemplateEngine import MyTemplateEngine + + +from appPublic.argsConvert import ArgsConvert,ConditionConvert + +class SQLorException(Exception,object): + def __int__(self,**kvs): + supper(SQLException,self).__init__(self,**kvs) + self.dic = { + 'response':'error', + 'errtype':'SQLor', + 'errmsg':supper(SQLException,self).message, + } + + def __str__(self): + return 'errtype:%s,errmsg=%s' % (self.dic['errtype'],self.dic['errmsg']) + +def setValues(params,ns): + r = ns.get(params,os.getenv(params)) + return r + +def findNamedParameters(sql): + """ + return a list of named parameters + """ + re1 = '\$\{[_a-zA-Z_][a-zA-Z_0-9]*\}' + params1 = re.findall(re1,sql) + return params1 + + +def uniParams(params1): + ret = [] + for i in params1: + if i not in ret: + ret.append(i) + return ret + +def readsql(fn): + f = codecs.open(fn,'r','utf-8') + b = f.read() + f.close() + return b + +class SQLor(object): + def __init__(self,dbdesc=None,sqltp = '$[',sqlts = ']$',sqlvp = '${',sqlvs = '}$'): + self.conn = None + self.cur = None + self.sqltp = sqltp + self.sqlts = sqlts + self.sqlvp = sqlvp + self.sqlvs = sqlvs + self.dbdesc = dbdesc + self.writer = None + self.convfuncs = {} + self.cc = ConditionConvert() + + def getConn(self): + return self.conn + + def setConvertFunction(self,typ,func): + self.convfuncs.update({typ:func}) + + def convert(self,typ,value): + if self.convfuncs.get(typ,None) is not None: + return self.convfuncs[typ](value) + return value + @classmethod + def isMe(self,name): + return name=='sqlor' + + def pagingSQLmodel(self): + return u"" + + def placeHolder(self,varname): + if varname=='__mainsql__' : + return '' + return '?' + + def dataConvert(self,dataList): + return [ i.get('value',None) for i in dataList] + + def dataList(self,k,v): + a = [] + a.append({'name':k,'value':v}) + return a + + def cursor(self): + return self.cur + + def recordCnt(self,sql): + ret = u"""select count(*) rcnt from (%s) rowcount_table""" % sql + return ret + + def pagingSQL(self,sql,paging,NS): + """ + default it not support paging + """ + page = int(NS.get(paging['pagename'],1)) + rows = int(NS.get(paging['rowsname'],10)) + sort = NS.get(paging.get('sortname','sort'),None) + order = NS.get(paging.get('ordername','asc'),'asc') + if not sort: + return sql + if page < 1: + page = 1 + from_line = (page - 1) * rows + 1 + end_line = page * rows + 1 + psql = self.pagingSQLmodel() + ns={ + 'from_line':from_line, + 'end_line':end_line, + 'rows':rows, + 'sort':sort, + 'order':order, + } + ac = ArgsConvert('$[',']$') + psql = ac.convert(psql,ns) + retSQL=psql % sql + return retSQL + + def filterSQL(self,sql,filters,NS): + ac = ArgsConvert('$[',']$') + fbs = [] + for f in filters: + vars = ac.findAllVariables(f) + if len(vars) > 0: + ignoreIt = False + for v in vars: + if not NS.get(v,False): + ignoreIt = True + if not ignoreIt: + f = ac.convert(f,NS) + else: + f = '1=1' + fbs.append(f) + fb = ' '.join(fbs) + retsql = u"""select * from (%s) filter_table where %s""" % (sql,fb) + return retsql + + async def runVarSQL(self,cursor,sql,NS): + """ + using a opened cursor to run a SQL statment with variable, the variable is setup in NS namespace + return a cursor with data + """ + markedSQL,datas = self.maskingSQL(sql,NS) + datas = self.dataConvert(datas) + try: + markedSQL = markedSQL.encode('utf8') + await cursor.execute(markedSQL,datas) + except Exception as e: + print( "markedSQL=",markedSQL,datas,e) + raise e + return + + def maskingSQL(self,org_sql,NS): + """ + replace all ${X}$ format variable exception named by '__mainsql__' in sql with '%s', + and return the marked sql sentent and variable list + sql is a sql statment with variable formated in '${X}$ + the '__mainsql__' variable use to identify the main sql will outout data. + NS is the name space the variable looking for, it is a variable dictionary + return (MarkedSQL,list_of_variable) + """ + sqltextAC = ArgsConvert(self.sqltp,self.sqlts) + sqlargsAC = ArgsConvert(self.sqlvp,self.sqlvs) + sql1 = sqltextAC.convert(org_sql,NS) + cc = ConditionConvert() + sql1 = cc.convert(sql1,NS) + vars = sqlargsAC.findAllVariables(sql1) + phnamespace = {} + [phnamespace.update({v:self.placeHolder(v)}) for v in vars] + m_sql = sqlargsAC.convert(sql1,phnamespace) + newdata = [] + for v in vars: + if v != '__mainsql__': + value = sqlargsAC.getVarValue(v,NS,None) + newdata += self.dataList(v,value) + + return (m_sql,newdata) + + async def execute(self,sql,value,callback,**kwargs): + cur = self.cursor() + await self.runVarSQL(cur,sql,value) + if callback is not None: + fields = [ i[0].lower() for i in cur.description ] + rec = await cur.fetchone() + while rec is not None: + dic = {} + for i in range(len(fields)): + dic.update({fields[i]:rec[i]}) + dic = uDict(dic,coding='utf8') + callback(dic,**kwargs) + rec = await cur.fetchone() + + self.close_cursor() + + async def isOK(self): + try: + await self.execute('select 1 as cnt',{}); + return True + except: + return False + + async def executemany(self,sql,values): + cur = self.cursor() + markedSQL,datas = self.maskingSQL(sql,{}) + datas = [ self.dataConvert(d) for d in values ] + await cur.executemany(markedSQL,datas) + self.close_cursor() + + def pivotSQL(self,tablename,rowFields,columnFields,valueFields): + def maxValue(columnFields,valueFields,cfvalues): + sql = '' + for f in valueFields: + i = 0 + for field in columnFields: + for v in cfvalues[field]: + sql += """ + ,sum(%s_%d) %s_%d""" % (f,i,f,i) + i+=1 + return sql + def casewhen(columnFields,valueFields,cfvalues): + sql = '' + for f in valueFields: + i = 0 + for field in columnFields: + for v in cfvalues[field]: + if v is None: + sql += """,case when %s is null then %s + else 0 end as %s_%d -- %s + """ % (field,f,f,i,v) + else: + sql += """,case when trim(%s) = trim('%s') then %s + else 0 end as %s_%d -- %s + """ % (field,v,f,f,i,v) + + i += 1 + return sql + + cfvalues={} + for field in columnFields: + sqlstring = 'select distinct %s from %s' % (field,tablename) + v = [] + self.execute(sqlstring,{},lambda x: v.append(x)) + cfvalues[field] = [ i[field] for i in v ] + + sql =""" + select """ + ','.join(rowFields) + sql += maxValue(columnFields,valueFields,cfvalues) + sql += """ from + (select """ + ','.join(rowFields) + sql += casewhen(columnFields,valueFields,cfvalues) + sql += """ + from %s) + group by %s""" % (tablename,','.join(rowFields)) + return sql + + async def pivot(self,desc,tablename,rowFields,columnFields,valueFields): + sql = self.pivotSQL(tablename,rowFields,columnFields,valueFields) + desc['sql_string'] = sql + ret = [] + return await self.execute(sql,{},lambda x:ret.append(x)) + + def isSeelectSQL(self,sql): + i = 0 + while sql[i] in "\r\n \t": + i = i + 1 + return sql.lower().startswith('select ') + + async def sqlIterator(self,desc,NS,callback,**kw): + class RecordHandler: + def __init__(self,ns,klass): + self.ns = ns + self.klass = klass + self.recs = [] + + def handler(self,rec): + obj = self.klass(rec) + self.recs.append(obj) + + def finish(self): + self.ns[name] = self.recs + + cur = self.cursor() + sql = '' + if 'sql_file' in desc.keys(): + sql = readsql(desc['sql_file']) + else: + sql = desc['sql_string'] + if isSelectSql(sql): + if callback is None: + klass = desc.get('classname','DictObject') + if klass is not None: + rh = RecordHandler(NS,klass) + callback = rh.handler + else: + callback = None + await self.execute(sql,NS,callback) + + async def sqlExecute(self,desc,NS): + await self.execute(sql,NS,None) + + async def tables(self): + sqlstring = self.tablesSQL() + ret = [] + await self.execute(sqlstring,{},lambda x:ret.append(x)) + return ret + + def indexesSQL(self,tablename): + """ + record of { + index_name, + index_type, + table_name, + column_name + } + """ + return None + + async def indexes(self,tablename=None): + sqlstring = self.indexesSQL(tablename.lower()) + if sqlstring is None: + return [] + recs = [] + await self.execute(sqlstring,{},lambda x:recs.append(x)) + return recs + + async def fields(self,tablename=None): + sqlstring = self.fieldsSQL(tablename) + recs = [] + await self.execute(sqlstring,{},lambda x:recs.append(x)) + ret = [] + for r in recs: + r.update({'type':self.db2modelTypeMapping.get(r['type'].lower(),'unknown')}) + r.update({'name':r['name'].lower()}) + ret.append(r) + return ret + + async def primary(self,tablename): + sqlstring = self.pkSQL(tablename) + recs = [] + await self.execute(sqlstring,{},lambda x:recs.append(x)) + return recs + + async def fkeys(self,tablename): + sqlstring = self.fkSQL(tablename) + recs = [] + await self.execute(sqlstring,{},lambda x:recs.append(x)) + return recs + + async def createTable(self,tabledesc): + te = MyTemplateEngine([],'utf8','utf8') + desc = { + "sql_string":te.renders(self.ddl_template,tabledesc) + } + return await self.sqlExecute(desc,{}) + + async def getTableDesc(self,tablename): + desc = {} + summary = [ i for i in await self.tables() if tablename.lower() == i.name ] + primary = [i.field_name for i in await self.primary(tablename) ] + summary['primary'] = primary + desc['summary'] = summary + desc['fields'] = await self.fields(tablename=tablename) + desc['validation'] = [] + idx = {} + async for idxrec in self.indexes(tablename=tablename): + if idxrec.index_name != idx.get('name',None): + if idx != {}: + desc['validation'].append(idx) + idx = { + 'fields':[] + } + else: + idx['fields'] = [] + idx['name'] = idxrec.index_name + idx['oper'] = 'idx' + idx['fields'].append(idxrec.field_name) + if idx != {}: + desc['validation'].append(idx) + return desc + + + diff --git a/dist/sqlor-0.0.1-py3.7.egg b/dist/sqlor-0.0.1-py3.7.egg new file mode 100644 index 0000000000000000000000000000000000000000..d0f4e5c4d96906bb7fcf8c235d44b7eda9be6541 GIT binary patch literal 56420 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 z z)h#PUYk>gjOaTy){x>u-qLz;AY*gmWny);iJq^`$Jnl$X>8Q;sBffXK5b`gYS-4M| z12hjOHjtSp!@DB7ypVMj_KGx~HqSjlAngzUp>1t(!d|&)5yTj3ruhzs>w$B^$vYJ) zh@uTBn#-4wIWaq&f8pW^5ed*acG*dq3=3;bn1MRIb0UitB!sIL%d)wlqY_Pq)#(<) zyx@|J+et{2w3MhP`08Mg#zfW%fomg@v7q|~*_oIrL7ZbyOQ}uf33il!6;FTmuiay` z41q|o%|s_0wLI79nI{7cbVEXM7aWj#8=#djtiK1@GFlX1VpN3rMSFT}cmn=-IAGi% z`jm1T1w)cNh6u(bM=33kjY-d zo-XVQ`8d&sQg#x|7^{KI_z#u|E1LA12ltp9M#u%dGXll+L-y$c%;F#Qy+b17#h@1A zGXS>(mPgY8VG38{_&`4eV9+q>s~kLw;g+&oblPIS#P*wmbrBp0fRhZsLPK>M_@}tU zq=>2*$|M#wWDdDvO(vIl&32N0>}-U2=`6pl(S>I**QuwtO--l~Gh9quw9(fP>-ziL z7eaUV3Ywv3rB`HuaMIib0*~U+=FkQ;i8JCbPQbu%SHtaML{QdhcPiObJ{7YcQJvWS zwV0zO>olJZ$r84hgkEnRtB`vV;E!cLRQHJ5dP*EexUUyL{i6wE6p5HI-V;zneUXdW zJQIgQN2g;j2km*a3!R*euYnRHzd{r-kv~W%w|ion@Bl_nX{$iYwYgEU zYKcs(U_3G^Qpj5};643USuEvLa5)~mVUhwn%J9{YmHJ%kCPGaExD~U-x`nTw~HM%8Ht zhW~wZUjkj7kY~i(q4{RV%aHxo{oe|f$Hka7F$C`CX6DZ80io}Q(jM}oz935?uWAEu4~Dw|zd*Oma@s zipXN={*}yHQJx1S4Imll+L0JcSw;K15%s9KqWIJnk_*&YdIb`_@N$UtwypAu)wzKh zrz0qi9@>L9^bM@1_d~0KO2Rf41*8l9i{H1tkY0e0o+ua?gu}G@xLN^#5R96myRd(L z$uiRFLQrJfUc|)*bMPK&SkWaxfC)FcESc+~euejnp=wo>qkn`S$~{aNyBVFjK2tpQ zy17flG^70tBC#`7&E^p#QS5LBkzf{O7X*UWVX=ZA)ub&tgoQ1fTAOCU2#nQ-DB-7$ zxh9hC+0t>X7j7s(^JBg)l0e_@33L%-Fo+6h#*YqZ`x@G)Fo2v2lPhg&$R`%72qF451W7d;$;LrIIP zmx9C{rXq3r%!!nbom_ipw>q)i<#fuzDg~1TqhfYNr_C7~W}a0(SNJ-M<%ld677leQ z?_>{0%I)4S|Gdj>W<5zU9xkl)KKZv@+zJY<&7wgmAA9MzMM-ZN?Byj?_JeFKlf=fmuHpEMT05eg~iON6Fq z)6Rw1*7?jYh9@maeAOm>QJaPk6Efat9<-o7p?V-|>$GU;RlZ{N_XI9y6$v;NwUEyd z1tlaPd|e0me7ObII?NZ|U;qPn=8$=SCl6j`xO?^^#58#9*eneCy8?p<@J73juU-%; zy&B%}Nfs);!eJImHh>RhA(}LRU&bsv7b8PBk{E@Jz84i(Z(=+7@K&9Di=b;be+cG$ zC}}m2gCA>OE)#Vl1oORPlyA=Ob2@7227{bm}$ zZqYOn`MsFXQXq4A)9ZbBl{R~5F8Ne%pTXfCAx!`^d(Rgh!G$?+t-)JbcD3b0Ovk2S zoxRNl`cMuJ@L)Uz48=RZ5Ad-%dE$%2YbXTYj_NG}x%N|}8?2p+(L@VP^iC=Xn&rHE z#zwTQ?3=NVj0z~_umsWbHo+7`stH+W^a|3PMXD-AqpoJNqGG4DBy7&oDzFNy6i(Va z9g{Zlaqd*(!8rascD|nZTO*JW*4?edH8cA;EADE>)HS=*WWR*sWSh^C2X;w10}o>g zVUb2CK?A39ZE=>Z{IP6uBh~%tUsy`ucmmvikiCocr$_MHH{lz_$kKE1^v}OMmj2P@ zE9F*RF#CBVh5Q`K|NB_d)3f(5GB7eX(bM}c#WN#&4`*{bTUuuJpSfHkr@g~NLja&7 z*kvPu2O}_&UWh?B0qsd35=D$aOcu;fRVI{xV8lWC?xMNLER@fpq{%&`?ySkKIjBu2 z=tg&DE!m4^xj%Bj9H2Y76l+e(Z4WkpwgzH|=^Uh97&vdNbnP9WE?fV`z`)voZ?cW07Su`yD+^X{xlf>nq~ zX3D!T)T$1Af!^XA?I!KOC)9;6iKp~Xtm^OR;=$z&|f9x0>K7Sv% zjMCwkq~^&aXM3zcC4q=E?gbnL6^yi&jgwSol$*tlue%3rp#?$KKK6`9addV{M^9vn zW9`MIPOul~FE@seST+XT5H=OeS>=+w$0|4;7cz&!i{z$@z(3F|^GeWmee zY`$$$b{ozdPN&(Rrh4VWNGynp;T}fsk3s2o4jFZ%Nv;GYJTj>|OPq>`Z2o(&$2Zb%*VSx109ol* ziFGR6j2{{eZsP=Mqxo%w@qMz~&uAk6`0?MopWeS>h53hJT}cK2K=D8R4gV>d_wVNK z)||2<8F9HjyMEoVPnkHpGjbEHZf+?gBlO}^ z-z?OTGer^57O)yf*p>Dt2^9uve@PMk3VcaZO3>0X>`lF(Y{fw#81<^{= zcyf03cH(*cxuv%FFu}|$3j0OJgZt-swxQ%xaN;xFz8ggC<&V65Orya4O$~TewHMbw z2O@^eJahU*D-ZOQseUW&bUquk{W@r^fD1Q8i?nXUnVh`sv^%r1Tac93Qf7!}0D6$+ zdzRbpvts=kpDpfRkB$u6Jv%yiZ!&c=JtsOvo}}v5^d8d6xv(kg)LT%Cm3_reCf7_0 z)3@#58EzqZd-osxz(>4KyfO4QQSdCjw-V2)yB6{Eee((5FY&uNkJ2*@lWKV;hZ6jT zTWTEZOG|Y8LJ`*0^(&r@*Uvsx8_18k5U8y_FPfdpsE7+u*wtbW!+&$OdrR9dRn6FM z3T@2>?3_h;&!Dm}oX@);llyD%yXP)hv^rxkx^O#Nw!37FvX%!fgZwM5unV>zyerW2 z{-C~YOAO;wFb-X}bkyR9j?L5XY@UYB*4&hr_aTYIfT(nZwdP@1j`*+3bI*!jAXksI z{fz;?nr;ePixa(&z&{nvwtBB=L+qol^@VD{IICzewHabVuZH)TopF*1TK3T11Esjy z#%M6Q6j`6y@`KWIi=JR~uqdGk%t?EmQ4xVXDM_Ofl6%ilO}2m0{_XBRT- zIdk@K&F}QRKE0UJ>9D!I*)2EM{@2ZO(-xuT8Eyy&p({K|Ee>7PK-9GVu>X69DrT#S z9bjnSF;lQr0lr`B{1r3+AoJRu-x^>SERTAoV+p1ngxvuy3R+#8q{)IcU}EU-qqClq z1{u&q)z#sZ2$`DW?}hl3U1@_DM*SC*T$vDJ08i|S1bCf*3o;}Wc&Y}WP#00cauusq z!1Zbj9r!0m<`5f^6q?9fG>N41dM3jQ7M%f-EFE&{?$&50lyG~bEQR-!k@o#V$`NMQ zIIL~p>d({^8%Dtp?p6P$m{C~gerMO<1iZg3yiBHAUiGM1z>|~1scYqFP=Ir92Hq!m zRsCvR0%$eQxtc`AExVeqqbed?xN!{K?}q zbv0n6C}R&kG-Qb96-1+k5+hj!yFt}oOwOlZiQ&YF-AeDx=WeHmriGg01jqv0qWw zr=sfd&3C?GJHsM!AkKfb%f+c5@CYZ*b0Mb@1$5Vcm?repzi?;9{7!LIJKs>Y!~6#( zgSKS5`72LH{Q2O00*J|&CJT|N6{K+kD)4x=ta`pnMRB#BDb*+m=UPbLUlWe|-Lrin z?t8)ZS#~SP3o*!yCF*Y#%n1{fp2ni)jO`Y8PaPpO&5TQ8Dr6(5h6s_X?Zc3ony<|I za2wkl?PQPpk{^?2JMKbz}ieFHZ`U_`_S^>f8eHL7Yu7-Nt zalw>?xY$bShvQqYpHXv;HV%4*9R*ibPfr=6G8-V=a0@^TG7Nrs@j_4-bWB#CFHoP4 zrG|vr)RE1N(2Xr7mpF4CFMMfXp|3vzqsBAaID?RW02)mi&U<4$i;)*Jyz*e|6qb`U z8dA>|L+uYwQQ&$}AWq|OQl8und&T;lG8hbDNbL19V^~&cRfC`~yMT^(TAv6792(VOm5cg-CPfcd=%qVh zH%=(CLIsW|J~I;3`c*a2+UO&q-dygOW1wO+ zyT73f96pthCAS-6vkmqr{EdMfH$S~x>ahdci_3d6q|fN`JvmWdWf$pf<2Rq*;v+%l zIr<~;iF9oH5epd9({?E1|2}L0c9T~yhmt41%&w(uUx|j9fi9@NP`Se2qM{hf(PzTE z5~s3eeiCpPsdS>+3SH|RqIKI_D&ROs&HI+J$W}`VoQ@1L9M2YCkA$a@_=v=_)Tf$% zde$Dhad7gQP=ZV}ao8-wztQgcW%UIiRy$@GbJD>sdJ5ek%6b2OfNkNkSb0kqd#7~{ zJkflBJq?z?j-3iyix_{8u4Y7)dsdae@yvU!BmjPrUuO^IH2yOOKY57*Uk5+CoExJ7 zoTrX&baDT~L1Pm|nDc7&t%<8^?wd!phJ^5boSA0hS!Ic%q5O6s)u234Yo<(()zqud zxtj~~pw{K6_8hCLZZ3EslM`c%w6mb{_918Gnq(ieQvX1RVjp&xgt_#P4J{$DZfCAJ zT&n^6c3n&TD$c}`k!X;GHC$pzu!)Here%iIQblUHAhG6@TycD*sa1SQI7Srp&6ytP z9;CkkZA*O>?SXj4>={hm)!n83K-w^SHSq<#Qu)Hr3wh_1QC-4|&5lV`5ZWbX>8%b; zkF!3qK{gS2w^UOmwxA8&z-p#QEgel;$Kq@vv~=l)y3n@z3iBneP+;(kXQ*T~&%{A@ z=dHK~pdmcy2Dw|8cPEA6jfmo6KKM|SMs`yRNw@5mLesme=ZT>Y#~8Mmbh9f-XD=K| zMl3WKB)j@UUfrgA>`k2iUfVo*ouw9a{HBCF9K#%HbH`O?Hg}$fIDn@$!uteKZ0=9f zY{blb^&B0~*&y~b!_lHIZ3%mc4BVASXwed3dK#;$*dWb%Dlh}GCv+lSD=*c4WLy=2 zzbgQYEUTJY@)$E8_*=(67B2KlYyy`guL+$3T8*aCz=)3e@Hvt!HY^n82?>L2HOIj) zYwcCQHeH<(iEd1WQb~7blTsPY9$4T!_uz)3G+f$C>8wiRPXrfZ02r>)>E3y~bKXKE zpbnk^W%by#k63tm%ziL92d72ns>9*^s4{V{<)mp~4t0ytLyp=SJu05hFP?d*KT)nt zg2#q?8Yn@e_n4X2Gx@0J*^oKz;E{d;aL0-pM;1=%#wwdrl-6=O!!2PJcb$1A>%z$& zWP-C2ipss@DxxOBtaFUJ;)zM0Ad!9xbHX8<;r@{+j44WH!TzgIb-ERD1UDuK_O{rY z?VjqPXt;@IZcTC;w!>SCTyHwEzl&np31wmQ86hv8iK2jKpb&NSOv)=tGLBNFd8SJ0S5wO9g z$rX+#U&9sIiavCe!A?L;!n+QshXhXuz_&>xexb4}OY^q3USVOVo017LV|hxLu#2}T zpD`_2&g@}>_$l+vXm==Yk&=p|W}AX;a3xfM?h?3~Qt?IH_b5`TGm{7`ap|orjq9S6 zKJtP+tg#KU)h;=?yM)L5M8R(c*=Nep{szl7#a~L^FXJT zh9%=FSk(ubymH_ST!DE)mfNzwge=)8vd6?`K(aHzl;&{OLieC`LHLQ($e(|oGF!so=&e$0nPU7ev%r={W$u`rZ85rMW=|#mgw!}x4Gl*JHtL-dllLpZyhEUg>+eo6 za0S_&AW-VdsV>Bv1(-!%b=%7$zF48atMyGhi-JcZGCqzsU?#NrlHWykzu8tcMqMQW z{Eq3pK2SjOQkU_^-ieZNSAg)qkog3P_Ny(YDBTZr`#lwq>--2s!25E>0pb>(L~l2C z_;LP44E`o_6n`(QxKBee{7TPp%uEYyLd>CGPpdi#4@6`S@GmLBD>FZ&1Adm<2 z5c(GqiwKMsx<5r|DY?{8*lfBFc6SuFW8J7U{l*u4sc|v-G*cwH3uBxjqEL1Z$QG%+ zrD#`SGGX{p@7%ExqF^BXjzjDh)5%HQF*V}W@Rpk53lmIh-isaWI!8^c^bIZl1^BG# z1iU+k%e{JBKz_Ok!JMG=6+I*Xcq>nV1adaI6FZ5_p1onPL8tCbp z#om7VyvUbYMQoCtiXicXef~ElP43isnwP{hh4E8YsU%Nq0*uS(Z={W3onWq|Z#Pk( zisjQFn4{IW36Q#5riL5NQwA`oClpsR&slUrX z&p3H!Eth}fFbfijE#`P~$M=6{Jqdr;F_6vL$=)TyW)Uq`-OG&O$uZy0-)}OW(&fqy zBs8c*uvwex5jV-`Nxp+RyM^Svsq+QVn<6UZ-um=2Ue< zqpiYr%;hF`IFN*H_Y(3J?amT+Cyf72Ee(haACXDyRXjRZQQ+_X)S_26_h)wAqPiMB z=$jQmWSyW$0p|IZi z&14?lS`l8@pp|Y)zof1Z!=gm*R9}48M<(g7MyxOFE9=>+E_t7~f1RlK2mdi~-B#}Z zQ7IJq5w2nWpMi(}zxeOJV-P+no0dNctgh@}q%*uh6d>d~B;6P~`O+F|plIgvw6Y^7 zY=O9x4BJmYx!c8%woSGKWrm8U1A4qCNf>s(yhug=ar#P9BArAOsf4-d!3 z{L?(wt!f=D3!j>st1X`T*W&|P=(_ikFFJrxT#*A7RB_s*2SO#V?%>fk0ErgJi_6Ug zTSCqypPMzgV+-Q6te^SpTpsJ#tdT+i%jYG-h%JJ0wZhOoI3pmr?-E6SOSDs_E;Nhg zZYZYx3i_~TJy7+BMcl&>qIe7s{zhj4VKKX&f{Esz$tSbH6-UVzg4;?OorfxzJb>+0 zNu|hyIvCpT_i20^Zg6`&BS@sQNXR49;Rz6pvxkRAj1n;wm_-5ETNSeJs)H`s!Ojz{ zwOsZ)4d?0X;8p}CYYk16i?%aZ!A*~tyL)(Q8rorYNIj7hJEYZMrJzbAbJoYCbqM8zq%M3(Jj z{?=#`6;gaD*eQmeS7kg_VdS)+F6AZQEM?~eJUFz!`D)P9goO>Yd&93(&OsUKw{YjH z5-OwHfOVBc?+}$G768ZFvg%8ruI6K%s;)e~rRp?5j$RcRhJ4>GzTsSs{R6Hbhj}HE zX4)`CF`Zg{*!N}pEgCj?jEhP(_ZyCcO3}xNETb~J%Gc&kB{GZg%y0i8SpFSZ-i`8(!1A9nW~umx+mygf+YK zdV0P*%+dT}yIe@m+zulndw?<6z<4n*=MjFi{uZbpUp$8Xc0w$B3bH*mfn~6>8$gRT zq}5A+y2iIr(9B2ikZ1j$^{f|NcCdO&bIN(GTGYTq4MBhHd}5M&M;wA@iy_sM*3<3^ zFt;1RsqyUQ>^cX413?YGbfJvLqVsrvU9HU?fUa6lY0^@|ym`k4?+Y+%HxFL{YKIzK zf#>%}clx-o*gn?62bfo5pFMzG2Hn6Yq1s#*Fpl$BP%&zeLKVo9s9eJq8<|?kS_p+N zDEIix`YKFskfRmJOq(!(Z51t3*Da&!P!X{chk@#fBWtg*jJ{j^(3g&dIt`DM`CW4*E_0*6SWuatR>m<#_&)%A{~V1 zecd8OUL%pjBiFtg?4cdOb!;WSmsQ+P@)AQ~eyeJ8A8+*SC?x1I%()bd9E!#}Y z-*vcmZ^KG6BM#Bv)VQ~0bruBUrrra@@yGSn%id;O@JuwA&rgO7ztEyDJfKo1ji!f7 zj9?>ao99qGtJkoef}t>1M@5#jjCD1Y+#=_iP;lJ&nl`Ipagaw< zR-itE`%4aVItnbuzs8rUlgCkniPp!z*2mnf z*J@-w^jzXtMc;Dt2G< zrh+Qb31f$-h;C^!jV6!!;aA;g6U2?5Ugot(l6Lfw-XdPT6=uRZVj|} zGi{7Js>@}(kUdcPs(=OzhA1~4(H&VThqa>e#)2#rKQp=Hl?#A)$M2=3rv2$ZN&e!G z8`qR{snQfvfG&+CCqlf}$qAeM%1??HLq?V_S?1U~G;x&1RAYS4S+`(a#uyF;qvne% z5e`(PStIc%U=e|db(T_1i7;d@1_SQt?{_Gppv%hX1WR3qgzs9_nL*fCJyc)>%QfkeL+q|F*fAN z=qKZIQ0XFJg8I`;s6oL%5gOY;JNX7is?7#M{!xMU$a8E<5vn&AM1!UUKlGQ!S|^cc zv+it`1WDKB1DjZi8noiNEtm1cLZA`Dm_N}?E|)bLJYWoBR?1KjeyF*TjHhK=geXDm zk&YL6`dsSl(K_Qde?`AyU76_>Op$f*s`-$G$bZObR->p6LgP)L!sm}`uYpcE>|C}* zCb=J!+Y3ApPWI+F5+N}L0wgex z9QD<0w{y)*tvOChX`oVNWGWHEqcG-^!E(>@$Btyf$E@^INY424tsdvfqx^fS(sld3 z`FMA((OZ9V5%9@Mi+wtg`?htsBpjB21Q>7lAhYkljoR;n53?k#cNk$WcpXIiw2IHm zg@wa&RUlDPm_uo46gU0`<$E=VG#7W;&o(z0r39qgmykd3F6!l9SQh_;lpAB})7B5e zBL8P~+yC6X{7=c~e~+PJ^(i~-5!7v+1fuo?>s7}O74jSoM@lZ1OO6nQrV>ppMXXh% zWGOkVT56hj=t=J>`>#iCps{A;Ln?mnRfJX; z>N&Br>Z2$hl0(tX>@kO+rN1@lqgYtzl*fMl0!`?^N?!8oxNthjf@#V2za5FrOyV*< zfy=U0U1}Fu(#to9%vT(m!MIb}tqg!cJv8pYqV02wPO$*>mDDPJLpO^-!bLwvuH%!8 z?R)0PE+FrjiD6VNAwPX!J}kFXKB0VDVje1+n}2}$SU%P8g!_t}zklGq@e9<=vm5x9 z)GS1&^eDj^Iic}M)eYQo&@8>4#XOOnrG8-gq$!a4p#sh1gD)g% z7D7YC-98sD&e_*8P=>Wh17M-%}uj@KAVlfpa1XYCWFhVv`mzzBOaAafQ{fvh#+w&MLL1r+hH!=nlFff~pL` zx}$Qy#rkk@N!UnvFVtzq{CAc-`pzh-(AFDBob=a;#_ir}*#V@DAnEj8rx@yy2|HB) zozHM-zz-?nh=Yy1&I3A=`2@S84njt5VP}6O&sev=BpSAY`ruyDyhHPXR`VlMJhqSb zeTP&FE3dXW=1zYZws+GPzOW2~p%fjJ7rH1fHsYwVGCTir#tPRG+yL1g=Sq9?^-K2t z=((oe_gm9H%uV*@#={9igHDRNi7iM z!9r|2$CVRd?<^yZ{x4T-yt1+Pj=IiM{k8nC9nm+Vfw?Gch1Yz9P&#k9V#o`5(ia+^YN~6`JDKGr((t47N8NZJAu(+AnypM zLIwa2LIQahU<|A z`M9g==rf+hZ4n%EnJtIQ)k^BNL&?F^b6ZC$;1_#4Q~CQFL_SY8n%scF6>H3U8LEG^ zxEGu2uXXKfQ67(4H_LF@%WID#A z*#eHe9{{*zqyIzrHR2kGvu4qYk)ITs0(Dpk9k;b4q+2tcXs?w;^tu-sg~ zt`#x?Y~vJuf@g=8mWjZOjpjBO5Z38bVIj12=m?h+^zuWEurqf2Nb7n;t7NBgF3DZN zd{a37jGgD%wKJ$b_%;=_G;9QD6@nig%qF>;TIyO`Fa&rJF&c!1sF!OfU;z?w?(e~r zVplH*T2eV)4-vXL5TS=8 z;<%_g&CQam5%;*ALI5OZ_H--AEYp2Hisg#g-#ke?MB>Bx-*q9MEGSM(>aqgds|es` zpRmObOp0)z-f~iWH|JjXNSJS&;NPO^zg@vgyI4pT4W6L zLk|}5^$5nIj=x_obJ?&YfJrzRwMQzmhDA?;T#UC!14B7`aqqu9qz4GO)nS{tLD~$ z)B}Y-8f6aA?CIh0V*UMwmF<7 zUwW0uY%^}hbMG#Kz4Mf*iBh_NNDe<6MhpO0D!0AWb&Xe#pj#gAWZ{voZX6vbz8l+E z96qf9JKEg}{-6eBe}4<@V&iQ9z#n;d+M77opPlT0ZGY`}YAQyYvS~|j3(cl1_*m?s zPqCeChA+>op(#_rl^l>DC$P>Lp|G z$rQR3hTN}0;u5Q;5sv1sA#f+e=lt0;@x^IB?j|+?@P>ycdm;wuV~dDRm*Un}OgL3j z7(Wx_b~MGwlhIG(m+@HnP;pqJrP$dc#3{#4>nq{KU|8$ODdXGD`jwzgJs%cGoH?z^ ztTLVn7L12auMBpVCq6)6EIks?Fy>U23iAtgchxAr)8@${laah9 zlBJ3(^ya3|kcH$rb(x@U>ZTbBF-jW=(oUoF0^(MDlX?^mQ$Sqhq;p8g7a%Xc|NYs8 z>_6k>1O)&!(MClICm6Ba8pc3L16`{5LkB1rCI+j=(-HDaw- zL{`um4ks*O*N&N$*3^26@Mn|PL_l~%%KYZd{CP^DaG{fQsqgq>8NKJQ&h)S5sTz8u zP3F9TCRn|GuBBduvi2O?=ScF!Dl^yC$(mBBnrbbyCC3)Q$sSl0{t(d;8`~Xb>FD2( zLcXCKpDiXcJRq59c{e|+A5eJZNm-U#vRvhjYDNQiFJjJCY9m*KOJ_$NXyo!pAbiVIFuR(8f*;!j>xgkE>Pi6>9chl;z zY-Zfb;v{ks!9W?9Cf$gJ!FTUJdx^WpGJ70l=K_lNRuk181!Ww0vay^h#j6QAMCNx2 zx~wmqwjvwhMGV7i$G}pH4G`7fJ%j!E3){{P(x1EII|a{4E7~>_GsVpnC!kOSgiR5R zW~`oxVGBNo#YN^(%*k6Qy@qa7`x39V+c}V*5s_a23S=LT;!eD@*j2LHu_~G}>upPr z<+^Gw8W2-!m=H^h&XI7f;W^TFraqXrE&Pso6g?RAp-<;XL5R$q3}tt;;HFU)`oSv# z=?RzwLfNjxG-0TOg!8*2Dcv1r!47`&tjUv8rTYtSMS4N?pgD?B*>WJYE4e)3tZVu4fF3WdO zM0_mmrT!j<*VjDL{$k`UK6j5?zkbwrQ}E=n{{rh$BQ3+Cxt^yfTWH@mypNyEpp!5{ zZiBUSVLg@~b+}Ma>R6{N`P|@gv4(YrWg}Ei5|kBTeh>{NnqmR#--`*bcdAb_O}Z>) zgPG2S+wdp$zeE@)C=Vw^h}&}paDqas%77$M$=`YqeMsvGR^JCe!3^{Tht@W(SOEsr zJL#y%b6bF6<*EC_&+m9UT1z--hUo5)15bxYr@aN*Mk2TqB#mTjeIHbKTM%m_N!C0xTp1n}QreQ)_s9L#^*4Y~bVRF^5ebVY;L`Kg({5p_ zFDKa+h0dQAZCjP*;m1WrKcI83Kkx5bcKlx-LGSR}zJ9q|OaROZ*SqW7v0L9pdFU0g zd%epQwc@iqTQq+ZRjBg&E3K#>2T+pYShec;tsdRiXarJ+{a=xs2v4e$?<&|Los}#| zrr3KHx)f{ZgI0X2=x_(^w0Cb+a#gb(#c(vl9SWs7^3fK;`0RCIx;L@ZZY{eghu zkwF}#AP31fgYP7X@@seSP-Y;82F~+A5_Gpg3|}l{@SF%eWQGpvUl)LDRhS(R*mVid;`pKOElD0(0$uORv?;w&30x6|Uk?!+KL>4O!Z@DRC8~kkFe3xt zd1Ki^Pf$-SNp&O13%04aML4(x-)+8%@qO57T}UbIj&q?dCoZV zHtrACQRjaO5DgLrEDHSw#D2f=BPMN3Z&2C>XVh+A1*lnlc}`I;8(kLvzV9ij-({Uj zWrh7c?*n}fD%-iaT)hK36)4)=>-(4GBB$6ZluLXKePy>36tU*C8_#!kvOWj8C??x= z=Jc35<*K7)cheCu>vjOeZ_j|JHG#AkK;e! zrbgUNeC|)L02J|mqf!0eod*A2xn}>Mo9kj|#}W(Zw4A>$Iq+qbTAHb;Rg#xhE^VQ? zS6EQjK&^|bvxQf{K!z|UwC{y66(eXlR)k@iyfpHbTcDdpy04qR1+Ss)W4+e=@=JZ} zLW?Lw0UNBu26i*q@3-E&x3S^w6$wOuyyqWI0l(pAnt zJDh8Hh{ck1izZJa6$`1IQU76ei>&VT2+%K5+t)5uK4NWQJ7?>b*5)7^CD_hkKeC?b zauAOz)cK8g3#bCei z8Et(nr@X?5If?))?3r{&T{&hoA5mihe{vFBSh;e_c4!A!XG@6VB6_4Ma(3%rXx~eVFZcy zm))nOHwwxdD!!%IL#)jc5NY(6#l`B3XDEyR{6`d}a2l=PsO>+6;wd)G$7tgE4L4hl z#q`3U>H|$veIT{=xp5wcle%-EOdicua~LPL!}<30aq{u2)dCCB+9zh;YYikB&fd4E z;@ZcvD&X7~HrSGYoa;x-B*{t8n5Bq734@qDU}_t7v&Gf-)OS*HLm(U2-BBjEtHMKoXD$%AU>#YNp1)1R$R?idYA6{nmA65J= z@UF%|+5(!XG&?fFAf8}9bFNR#kCG;&SG6x^&%>lJn;5i`A7`J4!>Io~VDmvm_=)h= zaxWtK=Pg?Y<<4zb$C@lIOe3x@mj7TYgM)y`1^K9Evk5A<7Qo*1B?#rCfoW&TIADEg zyT@ml?%54Wsd6NW}qxM{dSc+?Ih)$b^|GsSp!H$r6FsSwUk4Y zHL*k+^$#)&wjcd?@$OQyFrNE+iK;Y-uvOnwom1m<5P4nYnLRUZg%xXgD`E>0Dgr1@ zV%dxQ(-XaL*=X419AZP*rDk=pm3%CQ**R3J+e+^cs|*DxSVfBbV!ln{wa=VP7w_oo^h==_t@x#CF?rz%~eZADz zvGy@=A91_D0o&H}TJ|+$y!IAxv-+}#2*ROt*~Q8-1%r4d0vA7BOi3~>#Ko7yClqWNP+J13EhRCa#ZLiLk7f;ih8QoS=fCWpNBtP%d zy;zUiy~dhY!sC$6?HzLP+zYw~@Oech|0-5t3WO5iI)bL^ctVDr*g*(7uz)ch^9#g@ z%u*Wbiz(3V5_|#p@T7}=patQd`cBSkyglT}X%3 z1;(*O;cJz<8OpNF!eQ+wkPVqD!AdFygSr!q6v`R$1+sT!f(tyPoJ`fd;WE1>#tY8g z&0IaWq~(s{Is5^7Yc@_Nn#$(=ef5+ejx_AFm?+4i(-urd_8sJa%2B-6HLOv*E& z`P(awnfgu1%y5zk@O;yVzqB)J|-Rz^fO(DpM_qmN*u?(C>`Nc+Ej>;i@ zR~j+6kOxUJf3F%&(-8J}$?c)&(@(z@y^o`Th* zkPUHr!-;;f_A=5|fc*u&b+(eq*B;gfamdyVAKjeM?neywS z4MyL;5$5D?gh~6qQUU)z^YYsp_kT@#4B6lM@#G{5t%7WN?wE&V9oN;Gsw!2|a)HjU>3jh%s^1**J7@>GZj?yi z1O53o#*~OmN@lCJfHm9kdg$3bjK?mO8wdJK{<&|>T{{Kc#}_(~58Qp(KTS3u079hA zr*{=WJjJA5oONv|uva6{AnHa57a2xhj}>vG&1ZR_M{G$NB3OMd+HYgfz~RW*bt->Z zTY#u%>E*ABE~8>9lL zUkc!ukws`0aDb!ns{LTN^1-YFw#4E#tqEt_z4j7cM4UE?0{jf-;N3daLqh7SCpb4$e3G5!IM zvj$%x#ps2L6WUJjU!SA1KW3aW`&Ej*A28MZDM|OJ6x)Fd7EOK3c74MbuDhtqfamR8x;Y^*?y#5F?YCIdF(0Xx7^O=C}VjJ&--J=lC7uj{RL7Og!F+0UMP@3)fiA3dDU)STwV%;_Z}$w3HzN-v*NN3XOQg?#_^)iLFR-lT~Ao5Ycttavd|z3 zuiVsl$l8(&GMpJ6EqWc2Pry=a=e%M?%e;U?rWpP{6p+NymHd%LC|Q~e<*x+_f`{r6 zc=>h4XPn0M%a+_a?JMsK3|;u9^P^q*BZsJ!xwl|?VQLtJ(hya`>z_f64h&At4yz5I zn>X7>touUp{AhlvoYV(@yU`cIwv%_T7R`B>h5}bnjwQ()ixFf1AdRoB(I0Q39fj{d zg1hw~hvoch1{+G*GUhy!E-fPgH0_5CV6Lv(`FRUvbIj zD^l1m$`p)&%C#OO3n2k;(t#+S<55n+^PK6!ry30WevXAC6LR?q=wdjwH1vG^c~~eI zp^7?WiI!~E#BGoaiNs|p{TB(aJ7Cqf+#s55M?)C6I}&GFP;{uI4a06Vz3K3nOJV%L zrP~!Kx;&mP*>k_&a%=s5PEvFCJBrzsG_@2_^uqWeL;)VAyR&WDU_G&dJa>gB@VAv; zmLLaC;B0NIzYq|I1?`H%la@_+1w&Y(3&NjTmHxyVo7L9*>(7b#kc_1;dyHaGZ8+vB z{zoNj8BR;tQMy-zQ;>HW^i>(};bZxsyN;)+>LH^Si$L14$nFz$AG~H)?xoq;k-zNP z(zw{L9wVw{K<9e>po%h)- zPLZ6jhN7I2fyY%G@eg!B#ZWh&qpmZZIlva82%^9_dOcGEJ629F2ES#Z*?z$AJBlut z{x@d^<2>ACLy?=yRZ<;nh)SNLibE0YLIO<0LO>{ z)9nPDDE@=VK|Y0_LoB>ow&aTSu_;@vv8^gS#nP@I=H9(gV|o42N%bIvw5E{hw)ujB zu={`?0%0wf0gf9-74Htf;v8pqum)8y&s@e}>DycO3PF#jSX-t5eHPgQ%sj_l01T!r z{1MU2#S$%*HS(7#bzmumjlj2nYGkr5nNpbJSpUf~o*0h`Vg%=v2ao1UaDd}WkWY+! z8k0t_8wud?CD@fv5oiI&2P0+C*`=SKS{HG42u5$>et~Le-?diuJSGJAuO>)kDXI&* zfo`Dq-TF9F6lAH|Iu$p$z6vk;pe1tNMoRt$6L|&g=8_+>SyI&=;Y1TGe{?GA)#f1v zL%WHD0`D=C0M0$zghPZ4VWN$3jO2*{GY)o6n!WxVp`#dQsD(E@P)tLXd)1p2WP*u0 zX>ih4W-^I19j0Z-@i?T%_cZ1GC+il%^BCpTO8OWiBq`|iaR_WE=3kV{tq97rDdnj& zqCn0Y5cfyCO+-^os`(Xx zF}$Jq0Frt3zpxlQvw<(UTx_m;YuIPw;fD5O!hJ+ErDI7EJyTk3Q`}A&T1%G1R4We0 zkIxRXZdb@HGG_k!To!Cu!z=O?_7ZV;Tiz7ClZD2KM)&1N)C^zy1An*(u{~rKmAA_svX9y2TarPJ&fBmEtdb4Z>o4NB{_`8THIMyYN+{T zjWoMfHiILP`fD2X zE{8raGL@-aSXQ>QHgE21SWH|skxX{FHD9ze5Kh9nG**A=n^l$rl&zGtXik(gEdF7m z;?{B*o0^8AP}z3P%IWm-{D@=pt&!^MGu>h6u^o?{sz%B`70 z@-9@RH|3WiMF~yBZ!4Zq{AwCl2HIE#*iEG{^yx#!vwN8q&Y_|5*;M0feiMJ4bFM3z zq+3k)c1!>A=DR)seOI<9m)wQZntbH+W75%HjY*fr zyrVAl^sXt1C02*C4pWTLz8M*U-)t|ZxQw2cS?H(CKVbhe=Y09&3nz*N05He{06_A; zE|ULI*8I=?ahm(a347F~^94GLSV9>^R=SC1u;tfxKq9SZJ)Dm-`ED?tpunv}ikT7^ z`1@Lq#1ZcW9vK`UW9f;+WwF)4zaV+ZVqB8mvbdBkS!zReeI)&n&wQ%uI z^;IVCb924DFCY|gNWzTj(&^^q)oHhT^Wu7Ue^xfQw>!bSkLwm%|6^S4N2X2RfBb`t z5(v4YPfEs;N^74#TY*+xq*$HmToIEf*sQ^n&xWPStUO_VWyN-7+LC0$MH-eiXacm8@aFBMfph+up4fyI7Ku$nmCbq0sP8v>-1j z9^#<_KbdO;WZ6p}dgm~SHzVZN(}He)ZR0>8|L_wEx4d?*<8^bT8l6tNJE7nAee%KZ zCHka#F86mPJ6$&A`f%myq}nX19_8(vZHxuc2%qWj@DK))I_ulMNeVO_;_ZWpiZ4NjF&(SAye*z5?e|Wl@@)?fHF@5+n<(W+qEW)4%m;`0sep!;ULFqhwTRToQ8fVlq7GwE@h&6b7a@SaU7 zzDmUkD(*R^R*=t7OpWPf(_2($3rN`P69Z z+Nxn)#CW5C=VE11rz@D|yMvGqKCN%HlowZsQm^he93&qS==4BZ!M&0(4595VcxtKz zxjYcXYv#HIDOkPbgRkhe_{L-2qK z1%65j`Nn};Bex+6gagiTg=JMa2emxjze)7~Ud7uj--iPykP`rqoO7?wAiKeBd0b9A z9;=^j)hoUI%bcHX9Od(QIm^alNGA(WvvQIDg9WRN%AhEj`fr?}5Y5T)gUQwClxXHr zlWG1h1g&8#CfKZl$8Eke7tk=z<=yg^j%&bn_5i5Mo1qw2u$2w8i0yhiXquY)NxlkT z{+)kNn>?U{A@p)<--7PvuQR z0QB9ZRsjr%eNV*$EN)ZoGF)zD2BFFgB->^97&HKMtcB|Exhgiz9%zXrOWiRm!raaN z+ckIw?*jTHBY^zrW65BK>OG{tj^U*!Ta^Py>24fl99qVb!<>d7mdZy%6iX=>$Z-PM z4mON*uHAzx9+cE%L$>BlLq+bt3RFf?ydW;}=kRWYxJZjTjwK_*Tz8-g@gd(klBGi; z`kX+|RCi4t;HO{?0yv>=EgYn<{2UZd}+(P5_pkFu8t=7U8AenMS@+yKwsG*q*EUnOt3E($*A%BiTz15yXLe>q6> zQ4hEOEF1*2gWoY7B)fo~039^61KtTRYponX7*xIUaNQpQTwef|?0Dxq!7EgQm8q59 zMcyVUvC}mSA!RX?x@YmN_9q=usJT(N-afstNrg0oCcmM^Ih~-1QQ2nVCt2uyj1jv{DL)` zHS3b@d7_3(>)ipPX7w4lMaJ_3z35i-qqT&;xh6>P;$-EQ!x@Z=7>ig448!XNAgQsc z(OL6&f*SW_?mZ}=LV=v>T+)zuc2o3=X2`jO10KVHC_VV~OR#gWf41q}rQk5d|L&@s)*My*Yw%!yKujbtXMo1KAS}V(@&_>05pP5o(0WTI-&;wRY0cnuVB~Mec$WV# zqzK&-UUeDN#7BJBYSUl^4fKLhUy({6#j5ikTXS8sE5m zffz)%x!LP`Xc$Vk5sSX!VZqwQF}FOu;-}^F_Dw4CyO3*W+2PG)_8?>@#xy0{oZzxed{f4I)AW#S< z$CcGB=s293W0Mu=M^+8XQ}~=`gGQHp-BjSo?V$$~>f}5@!~2AD0bUw5k`6o27xia^ zr6ZPH9+_#ej46&eenWHBK%NM!&qB9#pd008BsK^l5J4L<%MIfplr(b|n75AAMX*~q z=0TOI=bGO(&d{Bqb7<)(LYJ8Cyt^DhGWf$_}|{vi%XUacF`4|uL-SRp45C4?|2L#+)13#6EDEJ7^q1LD-0 z$uD6_5r`W9dQDa>Poc4|3FzT=&#$G`I2vR&O047u{xhr0~~?9#Wn(oC7lA9srx(*Vo^~ zK92=}H;!W5Ays-=h>7*RzZwVU+2urv*ql!aU%r5~-%avAzS(su&I~(&QKY~bjX&2e zbZ8{HTyOjLcjjxQA@f_ z9UwxiwqWTbB2skWN>tJB4}u+j#{=0U%!rgYu@|WihQGL1P@3jGo(g$quQ7J?6D@2L zKQnj+i|7Lm0U;D42Sv1^%?&F8Knn1(Cr^KNPg*B>rj@C+fr_v^na9(u-5b-WHN|aH z*Qf1#w3+opGsC4EKL|#|_CBfh!^3`NL;9^Fm0q_L zU{enW8dh0k-;ES6D^&2%KcnBD<4&gjkcHZD9+hExU5G50+)q(5pi40D!V^O*aT3RL zOQm%KZi``a7B*cP2?TE~-2K3{pzbsxZ+tI5;^Cceci+L-;MW8(cPg)egP3+GpbbkG zC2x7S!GYjaQM-Eh9Z?)}jFS7+OTxLm2aQ9Qq>`QE+y;}R9WRVb%P^LSseUQYRnr_n zh|&x|Y#dI>=>NNS&5k8+e3NK2|-Uh86P}&}#bvcVtDoUIpUA)yX(f zh%>RJ`E5Tj$sPR|#w~g@2>vz5?19mG5aB%pHpUu>w=@erBD6DrH%@Yf+OPJC_gYcF z1$R6E!G{5J!Ye~C7{)P5@JI5e<%Y;@6%l)x2IS4PT-aj1>>?#y` z23Z-t!MAW($W&CpbPfb*Y+P&V8mNQHk#;vVgm~33HmkomMXTBIQxMP-*1a5q^(qz> z(WBhU(;K)Mq7Ds=LkmZk{xqp{h+XC`e!|#JPT4EIV^lOIot~0KU&tkvPD^+P?8eIB zmSaopxD%!YY%4#JYF|fsWx{qB#cukQvtH-pws*&qIjVn+X(w=cVMQaZzt8v$7 zBWxGtj)zfyQ%y`9%oe?!R`~~A*y@h20Z|kyU!}4WcHbzaNhfI z=3Da$sBY}ndhx0Nlurnjt+Wivh_Xo8RL3tTN3`Zgw>fW=cg_gqh~^^5zDzamR-Ms+ zLR*0iZ9cQ9ET_j4^)HSa{UgCtQ2ZX~1~bQUqpDfoYSZ(9G9HjH?>+2(1tv?^4r>Tt zbG!E$n%0h!^fjeES09biMCec#Go-4;h#h->2t7VZ2#?DBbnqY7w8|E&>k>D~h-HWb zR?oW1=I{xU6mmlgXjvqo;^x*jBiF_TDYk=Nhje5SXX5*T(LTyd0L81L!uax@1t_p9 z<*JK3O%^()&02XE8HVVt(JmK{a0qGg#bEnqW0K!xxKP<_B_VdW(9asV$iS^;lzr-hd?YhR)6-ahM%E^JPv zE{~UFyd$TH*LT(?esvjv{wPI=dCuUAk{;E!ITKumX~#IKML22j?p4})fAur#V#S=~ zRP-ARP!&|q_IU?2+%TlFJeZg3I9L;~3`vx<*quZX9v~7uEy|Oo9(t2}U-k@!sGH+l z>k$0#uQF!Z1|#Dmgl?ydLEOMA>Ro2^ThM5Ne zyZr~3jO@W@@o#+wJ6(p+<^eu63TH9>KlCXg^DRd57>X#o!=+D*3Wr$Q3PkeJur=K4 zu@fTb>k3We4hXWUbA4-&i?fXa!ETtG26^j}@>W9P)z#LeQc~O)NEEkzifoBD7?T0x3pD|5Um*Pc!=dz2ySTcViv96^i+vO}n!>Ye_DU5iR*8@ih_+J4 zF946K3gY`0%$8-e%OdH8HK_;IhQzyi$Nm@}aWHhGpz~xg)Dh;D9V|X)hdjO18((S( zlwf6%r`aXv&?qDODiy{Rhe6;hMFH>3hT=jJD+lRTM%0lP*lpP&Oy{q7f75N_D;$PT zR3Tu-@S$`l!!Dqi2`(}ySuxyN3E%*Lm{tFkI35Ngjd5_Z8YxVHR|4RWzasF^n&=xj zz&Po^=qNACk_S`vDV_3r*X(oJiySm2oHs98!<)G>Ctsg_AIA4BFTh46FKVU42)DTJ zOw||e=C2VLPhOr|bslVgujRN^J;^kh&FF|*c#Y7h_5f19Xpu4;?uRG5z%CM0nJ<($ z-y_NEaCn>_JFApG9S%SD=&O+<b)LfGF z{kOI!;8rwumr~qxki%p_c<7r$c&w6ew;nWEn*NpopWy*cbP+*C3$4s8x`rE>yFPJD zge(y{F@k@W64{d)^@O|TZ8=thK+7U)Zz#4|L(zxJRPQW|?d zxo@;WZ82%EqYGW4fRE!N+7|jIc>#@zI8vKPcsx4mT>1Lv{9MlCT9u`62fHGD&eukR zw=^L!n4fnz!YEQJF2q_|8nn7KnGs?c(-z4;79s&{vSmkZp%QJ2dea)g`wB#gVh!?1 zi%#>Vz<5CEb4k&|8WrCFz#3Fd97)AcdG4f8eioNw(T=LBh8|2LyadwsY{ymB9^mq> zgf9k(?N-Ws7jPwIoovwn0q22$fboP1mWqZpp~04Wn}=j!+ zKgs6c++sOdGbyoM?+R99j!Ob9d-!J~xxvo+A0XwW8*E}^rIq6jGVe*uz$3dbOrWb& zNR>2cl8k(%0tc8PeN5GntWW(_4->D4JBIt1v$39p`MzhIu|zt&QrlBnJA;RIl%gtn zbO4Woz_NJd4;ROUFsE5=-;RS|$XQQC5bwrgpcjtia?ve?MLQDVIs8ETrIT{()y^3n zWquIj3>JxFHvKA@)E{Buc(*CObdY;EJ{U2o z0zPq*tAkKxdA8p6V#XG9mc*TlUSHl4TWrg_2FPy0^@Z1X7%+s&YPH z#$I>m_Tj59mnp%PtHhf~N*L0}nEYZNY1_O+hM(>L%4hSE{}IJKB=7}Gpnm~7T#d3D z=tLnC?S$S0OIB4Y-krx9G%jCut&ugV?E{aRec=>Yeau(x>Q}8b{W#{B6Ow9iS(`tQbplaqsq^vsd_B{w2>{=B&9J!zw73y4soPk01j2OiIjp!u0< zGr`Qy&p$EA?1v%Qj6bR9D%?4(4m-sP-=FSom)!6SN9LWrE+A%kj?hG+XJ~d4>&F+L z=nYtjg70+<^%FHu6TnenT%(-jVo&UljH8BS>C!OSSA`f_y^S6j7Q5vJN!xs~p`>lYD-d~mBZL{RoA0sqz z8kA1`6)9izsNNAE)~JHvFh)!iL5LW?sNr3MySSjLxl{11!e2Y_D>8_{Vwueb*C!?? zuoQC#%!h*)`={)tvg-<;f!3|r;Dhf6qr25QaqM-pf>o#(jm+d|M7_3MF`brf*@son z5KD)5zPk|+PRQzpQ;g@6`=_u2!*NaW9t_`F!yFzB_ zGrbDX9r@aYZA&3FF)8ADliP26++Qp>5onmnriJ%;-K zB$)p*jH*`s6=I4b{mTL8ETYh8LD_Un3ci#;Bv&P=MaGcKqi8XzjnUD{tO|tflOPDK zB4e#247NN`J-+4aj-CIWQGQi*9Cg@Iv+!%P$6UZ~`{ zl{G!pUD$|8#VMXPa(aj=?Xix&$ewuo4iu`R0#{x3|7-fm@~J?5L;#P5Qm6Xqf#gD7 z4Vk4DD47;0%>bL~N2mIm-k_#&nu>R21ZlWmr>Q*Hqt+N$^t+@DHUO~ixzI#0z}x9_ z0@@8|si)+@_zgHB*$!-~%k=}%7D8sWV4a*-c{)FzcCA+zwQS+rUWNlGotKQ#78AE}8aV4t+$3OPtO zVG0 zBIiF_cb-H}Z<)bI)qo{{43ec^I|vEYM$b6TqFKt5L2EExI`V6tj^=3j-jQjBaHnv# zLqOb7>LaCZRcx%T&cqKRRR@Lna9K1;`M$4i(YEstlQ$`u3wEj0Q2{#7q&eR0z6!dp zTzxznx_yxsgBhnS+X$O1ouLl&E$AueJ!rTR*hUUzM&3EBCKZ=WTXN8Ztv(1lHb5p6 zRtyleaUlC^P5h=)jY}AjU&oH>FP49(xdl0$h2=@e$TT9(k%P^Tqi_jD+gGolH1Y@Q zCv%fhd%Ax1&I^kbl@D;}teR$VWeA<)`+$Pq`qK!Rg@wVG>1K*8On^^dxlPnG@SRBN zk2g+#+J0WwzZx`kow#Lt8=5d^)=_+OeuTzYY?UCWwy95p80c7Z_5rVd_7My$VD|M# z00^O^d|Ij!xs@TONIDa&NR0V?9^Y%|+gbV_l?|X;tW}7?XxM6OpZ~ zZ}!#F62At%k&AEeE|}%*S^s57Kjs|CGb-u~4W?sJlUcejOVQC#4f&eBKIVV9t}eoX79^U%bcD-$jm;*I&$ zPpFDO0L%_O5@-ENGDSJ^T)Ei8-lxE3U6`+~N{&v%+uVu=5m@*~f3B^&pc0mq2o17b zQ@@_*TT5+*T(QKMc1)yPi`v7DR*!Kki0-tvNSJ_f2S;HUW|tciL8t|%3cJ;}_^Mz_ z5!)6(;RxJ1&6%cxO91de0Yiz^4}oAc?X=-yzmgjdwBYXk@bWTCR!9Wx)zz`MmdvhV zYIxf^kgX0rAHP~Y`e)r1-B31?LmnkA4zpcG*rT2vi&y4Tqq%8SB`yQAY*&%xfmHJi z#(?-?%ng##gtdo-VxB~Yj?r%NC1L|=4E$AlAc8TKQGDS!snbX&lEEmJyXop*-+{K_ z64rWWJdb)pUt>HCPju2$8#WH`KkHo)4Gan`~VzY9FLijh&5g5JAd?vkM5#nE4Ba;Y{$>I1VXnwA(d3xB%ONz0 zwjBm_31;>R9^fKLlbn?o-m7xT@{tKz6aI)DoO9gn&tv>*65$oqGe+otRkDG6)yC>2 z$*nL?+kL&Z=~Bcwm?m_5mfn<4*11st1kI#9>Zj@8jP=bw|Cd=uBr96Y9^2%Y6ddki3_E47niMG4EUQ@Nuu{Uz^+>>pxoopXF6d-@pksW2Ii-4=I~NfOymIED9^ z6nuoDV(?)R2f@hRYaIv0$i6$q4hIR@UJT?}ZE6Wf?}#bhJ77)p6x1`cBq<{q|PZg0{`Ly;xK>^poZ5^Ff0e z>|qcQ_b(3;O4|?X_wugR`Hq1;=P!Z-d_p;F9=x?{*soz zUHSU7EyeLu(VR#1AP~^6eNNw`V=e?xTsW%~%9cdN{^l0!TBB_<&811gpo zTy{(dub$RMBT79+{1X|@JCw2UgW*2zF+5q&k)fd;Au}h57BiD&FdA=j0a{hdcW;=} z9m%%FvmTUuTHj)XMnllcW%+M0(Bn*5#I20GHVvH&x?qTHg)^B3VUaLGm~g}-hh)j{f^bF@?{b_tHkEF| zcGsmyUHvU=^d!O`=qCq1ogho{Fo)YSZsW_`WZN#c9G`G`)i|GUh!4N%%Mk7XrmCNG zYpy}FsI%<>7+6W|Vp!1>wC;~MtsSJbY9^wdCRq6-IB~qOdlu8|Q-;k2eW_g~^jBn~FFv|wEtX%Yzv0$}PDdZ*6bu&B2(Vk50nBWY| zKyxf|uZzSHH;-|eVs!N#*MrlYgRn?fO|r*|Tmf}5M?gouO385zS=pdR+fKLtE}-5g z^(ugkcf-+iaR-kAS{M)Sd?zhmT=T-rARsIM!9hF{Ceyng(0XMyD-qh_Rhi~?+|drf1wbJSWpLbnSO^qGEw4t>L_uMqkHZGIEZnB z4l->AkQ{g@@{y%`9CWdfM)o`;IcTH%9E5V=n2wC(!{}qfQ<2I?h9^T7&Ws0`4q&m$ z2O4*x9CA^oLl5@eB3%T!!H0zh1sv46fy+E(bP=h;)b_3-Q&DRB>xTzf?y#{n?mAgv zhsX!g2e2G`lC~GJoIqI`ZMC>QH$ASW6gd2CwJve0)xmD6KsO}&^!bPZsHrNGg1hP( zQ3zvCV*(s;GUl=@bq%6EjxN^M480-ZVS9Ma4kT<9w7YRnme>OLLl`v`!UAC~F3oV^ z1ZW}Xe~d13s)9U*Jlt)%SDq^aMDj;9o4JfAMY2XuXxQit9+=o_VFc+!s?n8X^tU!% zRuz41>7K}e?Prut3Cs|NkPj0%j)M9y^ho(DxfTL!f_*aaN73?^k;5uQl0|-K?s5YdG2G`3uySRa|6DK z63W>h##h46obi7v11}X-Go#(e`QlVy}mjsZ&5@%sWrhTrJM|wokt?@F)D!zUXgF-)GrslfL@R zqB{eMq&MlwC1dTCXr)`eM=d#FP@KtY65T=L{$doosuHvUhxvHz3vF9`8a6LR_yyA& zy2LkPuCWD0^BgB=0$UNKZS(T;ATqg`w#JiiE18z;%TPT4oz}~lB4-s1Tas!?+ARv( zXqW={yhbzWFg714on)Qa!wjIP;Z1phf`+kA{b??9w3GlINepFpw;Nz~hnOq8p}oNz zuk!Z#{s0P^z9w9t2z4j8BGA_FtrNONKW%gck+3QZP>X7 zpCJl3X9DhVzp$pF90xyBCvkpk`Ekk$tw;ziv17r1=ndj~XBI5boL_pw@!n&9`Ip`} zTM=Hh3!y9Fk0@+2)2 z5{ehcOeRFWH^deaaWUOvXb2W!IFEq~hQx(H-UG#D!evsA(8!%KaDp7xxQb{rri3!G z+1Ioth`5L28$>+*Sj#h=++15$2SxcSZckUOM5}?28R-3TE)<#~O#>N_&e*@43+TtD zArY{dl((N-&FP+fF3l!XLLcJm^A`LF>HvFW=uex8O|(GJ(*xU~?@y=R*_8?1Owl?V z(qlN5!O-zMQm6>`F5_SIgv&coCMg9!kkM{*V2)5M zzhz9+3hbtDExzWD19uX#onQ{)Owb#DuT$VmuXA%|rpxP;+nJ3V(Vl(9T(8*;7?8-s z@Uo*L?@6~clMsBsIK?r;j>S&Z5Q`hAZs*>UNGugcK*^S(<>FT|hfGq!%3&#hL5QxX z?`gvrUs}%1k)CI~n);dYSQC8D#kXP^MIw@Pp-WiNF95PD6x(S$2UmvGxC zX&>V_uaDZrD3f4Zk?d@b1%?aA69p?#K11i6_4|D54pidhG7`B@rLKrY-z#N)^Ooi? z%xO1R*&<9o^I}PHQUTHlWOdr#*KXMuC76nr=iO z4D0(>KSR`<`AqJ^bcTghvZe-sv@;^TN4!a8}?DM0xNJFhI9e=nC2xO~YPn z`9J?EClWUaK|WLb`Z#yln=vNXUhO`zs(#JZ$SXKrz z+-Sz$X}5c*620JWDVJx@D~Z!mYsj^!4cz5S zG`1b1A3*Jg6s{b~4ZOZI9DJ7pZK!%}5s!)*Ni=180Pn$6A-oNe47UNTx143lqAoZU z-jF^F7*k$Z5~1i0v4Ry1-`9P@#Wm0v6}p=;W8>kWF0C7cpYooZ;6?#(z_4qu=vRq! z@Rr`ctOl;AlIi>;;`q99l^;+imE+=ddS67Vl197%?-pao%_ z-q-;^f1sVxbYj<&p(GY1?s%x40=fLKXKNQZhi2TEqpqM!DKrOPzvVUP*?22Zy3GQG zg_UJ^(M)lUMZ!$#K&IOu&Iwy$X$;<^l|Z9`hCnK%+f%fuhz|<7QxsnFTM={og5;aIybzgsyLa!Kl78I^!mjaRq?n1#(_ohRu1GK{fL8bZNj@c!(Y zRH(7H{@RsMpFbRM4{Brn<|7i>KhTcJ{gVkYYv~`kra{{v<^pSBhfa!Wx(K1sNZy z$VkiLU-nt7DaCMT92UgYkei$q@c1SXH(kWa(f#+2*l~M_i1%e#dN(?=0>+@HN5i0} zyl#7j#!x}E)G8j^dX*l?Q81NxXt0zUd!t7%$ob1kSjZ~`eXeZB6Mvg;r>26&--K5m zGiVk>R{phLL-}pjSvM!u9BGHAne~r5HS?{YayG-h$zLSez<=@EGnq!`A547kb%DQ8 zrPR>D8V}>l&)xbMf3+jq?i~x8)JaA2pVI}G+*KVwZ}uXG)f9D?sEb-&)K zqhQWdT9%d0GG5xFeEwm`6C4XPcGv8+rjIg_wQVwGY1KMiscqi*vzAZQz|M~v!Kpyk z%ol=#BgfOV%9DMAj3?yPjtvw9Gr>$lQrfQyI30#^XG^a%GI#+o@IGMOOJ&L9MStEeE|z9(?;)8pava~* zYn=p&OA*U;Om4;+*|Y72yaw!Q7Hkm>ztR1`V|8J3emH^X0qqI7TJZIvf@o&0>6 z3Kt^uTuk7t-)X9DKURs&d^)Y_2xBwCik??@tYR3bV6yJu5?dHnZ!BA}p7CF{nu%f( zfl_tD1{x2nKf~E7S%=HPR1H^@n3UFn&NAbr8uwts?`qXVYtI^7au)jB>zpJGfq!po zey@To^;Mi_QRvH3sN)i&PPY1S@6>G@j7pyIjA7%?12M??QtL=wU23()jHUen<M~~?7CPpNHtt@!Q1M*_?R9&u@JQVKqjqn* zp~`*l_)N{#{;Gi9|n$H zIDobY{Xy;JVX&~T^)%V-I%8fJ?@Y5vtxSbYOCX3+CQQ3 zmxD_7!di#P64@H5`_7H(eQeaDos9e86Rp?%V4uIE$2H4Z2HX8*>8dF;d1rcE$8YK& z4}PHUG#l7`r)lKoRb#4iy zvU#;9^nJB&mPEWNQuoav;T7I;#>$fN3P+3i{P;Cm+%g&8faio9TS7$@6UWVqy%x1^ z^poW~C%$E_%a0BtmxsPb@yI~SzX{EKdX(^^B`C|CQxNUPMWaikMESahlbl(rZSBtCy%*MThT+%+2=MOpi9zDz7cx{yZW|egVUI z_@U}p4bPKW`@AyYo|S=sXhBzFMUSKHFPM+Gb@M~h^RB3+Ntu-o$Sxa)<&UK{DJ9LZ z&6sO9y;2Dib8F!1k!;rEQFcKnJ0tF|FJ<-(M2&r)+xuQ3u2wO%A&pCrYobt3T$T2T zvcZVidF+=c*G|ZsO;)R|_s2W3-tv9Y@JO2FTcI;??gMGE{Mk}|c1w_ug6kF@yo)Bi z=s8xk(8u~bWh?MBs4K&R3zCPH=jO!SUz|yj7dp6z5W)F~;|6LWTFqmgchYW#w1yfR zLRVxfDc|Y~(Xae45?Y_~+a+zo>gQt#(bURqzTh)`@p6rqK-AY)>!|u1glsQI(#%43 zWDdRXbUTJy&M$?dSFGAEZpk~Kg`B@epkH>d{CVCz*+4f`4Y%~PQh8psQQ=;f2MQ>& z#yd%xNw;SC9|^f%cN(CMUQBAfS7;&lx?rwE%xtC?H{YF%Gjp-`f93dLA$qg{WZUw; zY70k8>}9=$d+RRHH5^?go|D7nZe%;MKeeTDWWS4#PwbElXS};-3|vV_hQ8?nZR4%D zIPp^0MiQrBz z?22A7p2Aqt)KpZ~T8G|T@7NS1z9AXToNB@tHPUy$sR^fCFQpk6wmbX7@l zb>+NdQOui${z5JO>eBP=OSntJ0asNwUpkVX*^xy zY?CzG!TY|*(5c5H`b$B(NIB2SHw29Sj)BE0`|_N}T~+G>Watbrd{m4l$cRmAgt@ksZJNd0i(`rPW|@zz9Nmp(PnhAHop>O z2)q8e(ZfSUC;wXC1<`AB*l&rYQ(rxJY&u%|0^I3Q@M)gu>xCK?N1Ed*`d|8~=_)l3 z)-sNE{rw)lX4z4YJ-^5OO2S2TEFbwu;5v7+INeok*+8|1`gKjAnj33QG!w=Gc4i?1 zxavJdYRUWT9;L^6->X>f@Zocxmp_?7n;?*~+amtu3e%0lLAK88(w7$a6mT86bgMs2 zHBIBY3ZDhM5k@|Zy)9ok@1Wlx;qg#a^zVo>z17JKu##aXHr|%Haao1(yb}+Zn^OGP zc>8sXPsCcho&ZU4_oA8g*caSQt0ZKUkd_-u3Dl}SCUt7fue~~S7OpMD<|!%^Oq@5js>nZ zC9F5%*YN!GN~^d~c0sjH@5;B|%CxSA#w(B$=!>Nl%kQ?V8%Cy zPo7H!Fd6nUR9b0QQTWh7CyvEK_7!9XZzv@4HgcUo$k{2_!TkwZvW+|_)yL1Ze*vFv zK*-2I!ZSj{Vm98MIC=7*E==1<+F1YB8&dg))#jCA@Wt0az5(5ASj;|fh9EaJGCQSt z!c@xD!%_P4K;c z8nL_xoc`wqkhFyb$^qqSVIk$@xm`|Sp9?b;wc@e?Vh?bz{i6Kyip7i)7#nlK zO}R<2x#Z;Nh;|_v#ECoUu%+ZPk%pkVcu-r4mq^`0M^OJ>WRSQU8z6;UdlwG^P%HrP z5rIyDT`s={W3wIfZ)vvQNr{bSmV$sJ4@iPUq-S7!JCL>HeON|90SyNhng9`v z6HM9;G;r$QnnJ`~yX%q*dypqT6*k&~qVs#8q2l3$Rhk-icH#IG| zn0|F-=hYGq)KO8NB;JD=O$6>FUag$qQTAVILb5|*x+7(6AUj zu}%4wBf(Sf#0V9Wd|2z7d?7ku9VtmhMo$22tTbDhO~l6Y^Tf(@V7$v727Lt9^a+r} ziIqs1ph{2@wmcE*JHVBa(Pd)`$nj??X`#|BYNhp#)a`-b zpCwO{bb-{{q-7ts476STPXjv^fh4s+T4Zm_LLKTqEo@D|e~g2)zS)+Q0`z}c*@{em zo(7F<2c+9Uw}{b&I}!x<$U&kxI2uV V3jD<)BZGpUKu{r~xqai){{ZMD%}M|O literal 0 HcmV?d00001 diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..a5ba5e5 --- /dev/null +++ b/setup.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- + + +from distutils.core import setup +from setuptools import setup, find_packages + +# usage: +# python setup.py bdist_wininst generate a window executable file +# python setup.py bdist_egg generate a egg file +# Release information about eway + +version = "0.0.1" +description = "sqlor" +author = "yumoqing" +email = "yumoqing@gmail.com" + +packages=find_packages() +package_data = {} + +setup( + name="sqlor", + version=version, + + # uncomment the following lines if you fill them out in release.py + description=description, + author=author, + author_email=email, + + install_requires=[ + ], + packages=packages, + package_data=package_data, + keywords = [ + ], + classifiers = [ + 'Development Status :: 1 - Alpha', + 'Operating System :: OS Independent', + 'Programming Language :: Python3.5', + 'Topic :: SQL execute :: Libraries :: Python Modules', + ], + platforms= 'any' +) diff --git a/sqlor.egg-info/PKG-INFO b/sqlor.egg-info/PKG-INFO new file mode 100644 index 0000000..0a89f2e --- /dev/null +++ b/sqlor.egg-info/PKG-INFO @@ -0,0 +1,14 @@ +Metadata-Version: 1.1 +Name: sqlor +Version: 0.0.1 +Summary: sqlor +Home-page: UNKNOWN +Author: yumoqing +Author-email: yumoqing@gmail.com +License: UNKNOWN +Description: UNKNOWN +Platform: any +Classifier: Development Status :: 1 - Alpha +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python3.5 +Classifier: Topic :: SQL execute :: Libraries :: Python Modules diff --git a/sqlor.egg-info/SOURCES.txt b/sqlor.egg-info/SOURCES.txt new file mode 100644 index 0000000..6c43cc3 --- /dev/null +++ b/sqlor.egg-info/SOURCES.txt @@ -0,0 +1,21 @@ +README.md +setup.py +sqlor/__init__.py +sqlor/aiomysqlor.py +sqlor/aiopostgresqlor.py +sqlor/dbpools.py +sqlor/ddl_template_mysql.py +sqlor/ddl_template_oracle.py +sqlor/ddl_template_postgresql.py +sqlor/ddl_template_sqlserver.py +sqlor/mssqlor.py +sqlor/mysqlor.py +sqlor/oracleor.py +sqlor/postgresqlor.py +sqlor/sor.py +sqlor/sqlite3or.py +sqlor.egg-info/PKG-INFO +sqlor.egg-info/SOURCES.txt +sqlor.egg-info/dependency_links.txt +sqlor.egg-info/top_level.txt +test/test.py \ No newline at end of file diff --git a/sqlor.egg-info/dependency_links.txt b/sqlor.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/sqlor.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/sqlor.egg-info/top_level.txt b/sqlor.egg-info/top_level.txt new file mode 100644 index 0000000..68b15ea --- /dev/null +++ b/sqlor.egg-info/top_level.txt @@ -0,0 +1 @@ +sqlor diff --git a/sqlor/.dbpools.py.swp b/sqlor/.dbpools.py.swp new file mode 100644 index 0000000000000000000000000000000000000000..41808d26dce7576babc492625d1dead93fe799d9 GIT binary patch literal 16384 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 literal 0 HcmV?d00001 diff --git a/sqlor/__init__.py b/sqlor/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sqlor/aiomysqlor.py b/sqlor/aiomysqlor.py new file mode 100644 index 0000000..26c6fb7 --- /dev/null +++ b/sqlor/aiomysqlor.py @@ -0,0 +1,7 @@ +from .mysqlor import MySqlor + +class AioMysqlor(MySqlor): + @classmethod + def isMe(self,name): + return name=='aiomysql' + diff --git a/sqlor/aiopostgresqlor.py b/sqlor/aiopostgresqlor.py new file mode 100644 index 0000000..7094ebc --- /dev/null +++ b/sqlor/aiopostgresqlor.py @@ -0,0 +1,8 @@ + +from .postgresqlor import PostgreSQLor +class AioPostgresqlor(PostgreSQLor): + @classmethod + def isMe(self,name): + return name=='aiopg' + + diff --git a/sqlor/dbpools.py b/sqlor/dbpools.py new file mode 100644 index 0000000..6ee08c0 --- /dev/null +++ b/sqlor/dbpools.py @@ -0,0 +1,277 @@ + +import asyncio +from queue import Queue +from functools import wraps +import codecs + +from appPublic.myImport import myImport +from appPublic.dictObject import DictObject +from appPublic.Singleton import SingletonDecorator +from appPublic.myjson import loadf +from appPublic.jsonConfig import getConfig + +from .sor import SQLor +from .mssqlor import MsSqlor +from .oracleor import Oracleor +from .sqlite3or import SQLite3or +from .mysqlor import MySqlor +from .aiomysqlor import AioMysqlor +from .aiopostgresqlor import AioPostgresqlor + + +def sqlorFactory(dbdesc): + driver = dbdesc.get('driver',dbdesc) + def findSubclass(name,klass): + for k in klass.__subclasses__(): + if k.isMe(name): + return k + k1 = findSubclass(name,k) + if k1 is not None: + return k1 + return None + k = findSubclass(driver,SQLor) + if k is None: + return SQLor(dbdesc=dbdesc) + return k(dbdesc=dbdesc) + +def sqlorFromFile(dbdef_file,coding='utf8'): + dbdef = loadf(dbdef_file) + return sqlorFactory(dbdef) + +class LifeConnect: + __conndict = {} + def __init__(self,connfunc,kw,use_max=1000,async_mode=False): + self.connfunc = connfunc + self.async_mode = async_mode + self.use_max = use_max + self.kw = kw + self.conn = None + + def print(self): + print(self.use_max) + print(self.conn) + + async def _mkconn(self): + if self.async_mode: + self.conn = await self.connfunc(**self.kw) + else: + self.conn = self.connfunc(**self.kw) + self.use_cnt = 0 + self.__conndict[self.conn] = self + + async def use(self): + if self.conn is None: + await self._mkconn() + conn = self.conn + if await self.testok(): + return conn + del self.__conndict[conn] + await self._mkconn() + + @classmethod + async def free(self,conn): + lc = self.__conndict[conn] + lc.use_cnt = lc.use_cnt + 1 + if lc.use_cnt >= lc.use_max: + await lc.conn.close() + await lc._mkcomm() + return lc + + async def testok(self): + if self.async_mode: + async with self.conn.cursor() as cur: + try: + await cur.execute('select 1 as cnt') + return True + except: + return False + else: + cur = self.conn.cursor() + try: + cur.execute('select 1 as cnt') + r = cur.fetchall() + return True + except: + return False + finally: + cur.close() + +class ConnectionPool(object): + def __init__(self,dbdesc,loop): + self.dbdesc = dbdesc + self.async_mode = dbdesc.get('async_mode',False) + self.loop = loop + self.driver = myImport(self.dbdesc['driver']) + self.maxconn = dbdesc.get('maxconn',5) + self.maxuse = dbdesc.get('maxuse',1000) + self._pool = Queue(self.maxconn) + self._fillPool() + self.using = [] + self.use_cnt = 0 + self.max_use = 1000 + + def _fillPool(self): + for i in range(self.maxconn): + lc = self.connect() + i = i + 1 + + def connect(self): + lc = LifeConnect(self.driver.connect,self.dbdesc['kwargs'], + use_max=self.maxuse,async_mode=self.async_mode) + self._pool.put(lc) + return lc + + def isEmpty(self): + return self._pool.empty() + + def isFull(self): + return self._pool.full() + + async def aquire(self): + lc = self._pool.get() + self.using.append(lc) + conn = await lc.use() + return conn + + async def release(self,conn): + lc = await LifeConnect.free(conn) + self.using = [c for c in self.using if c != lc ] + self._pool.put(lc) + +@SingletonDecorator +class DBPools: + def __init__(self,databases={},max_connect=10,loop=None): + if loop is None: + loop = asyncio.get_event_loop() + self.loop = loop + self._cpools = {} + self.databases = databases + + def addDatabase(self,name,desc): + self.databases[name] = desc + + async def getSqlor(self,name): + desc = self.databases.get(name) + sor = sqlorFactory(desc) + sor.name = name + a,conn,cur = await self._aquireConn(name) + sor.setCursor(a,conn,cur) + return sor + + async def freeSqlor(self,sor): + await self._releaseConn(sor.name,sor.conn,sor.cur) + + async def _aquireConn(self,dbname): + p = self._cpools.get(dbname) + if p == None: + p = ConnectionPool(self.databases.get(dbname),self.loop) + self._cpools[dbname] = p + conn = await p.aquire() + if self.isAsyncDriver(dbname): + cur = await conn.cursor() + else: + cur = conn.cursor() + return self.isAsyncDriver(dbname),conn,cur + + def isAsyncDriver(self,dbname): + ret = self.databases[dbname].get('async_mode',False) + return ret + + async def _releaseConn(self,dbname,conn,cur): + if self.isAsyncDriver(dbname): + await cur.close() + else: + try: + cur.fetchall() + except: + pass + cur.close() + p = self._cpools.get(dbname) + if p == None: + raise Exception('database (%s) not connected'%dbname) + await p.release(conn) + + def inSqlor(self,func): + @wraps(func) + async def wrap_func(sor,dbname,*args,**kw): + sor = await self.getSqlor(dbname) + try: + ret = await func(sor,dbname,*args,**kw) + return ret + except Exception as e: + print('error',sor) + raise e + finally: + await self.freeSqlor(sor) + + return wrap_func + + def runSQL(self,func): + @wraps(func) + async def wrap_func(dbname,NS,callback,**kw): + sor = await self.getSqlor(dbname) + desc = func(dbname,NS,callback,**kw) + try: + ret = await sor.runSQL(desc,NS,callback,**kw) + except Exception as e: + print('error:',e) + raise e + finally: + await self.freeSqlor(sor) + return wrap_func + + def runSQLPaging(self,func): + @wraps(func) + async def wrap_func(dbname,NS,**kw): + sor = await self.getSqlor(dbname) + desc = func(dbname,NS,**kw) + total = await sor.record_count(desc,NS) + recs = await sor.pagingdata(desc,NS) + data = { + "total":total, + "rows":recs + } + print(len(recs),'records return') + await self.freeSqlor(sor) + return data + return wrap_func + + async def runSQLResultFields(self, dbname,NS,**kwargs): + sor = self.getSqlor(dbname) + desc = func(dbname,NS,**kw) + conn = await self._aquireConn(dbname) + async with conn.cursor() as cur: + sor.setCursor(conn,cur) + ret=await sor.sqlIterator(desc,NS) + ret = [ {'name':i[0],'type':i[1]} for i in cur.description ] + return ret + await self._releaseConn(dbname,conn) + + async def getTables(self,dbname): + @self.inSqlor + async def _getTables(sor,dbname): + ret = await sor.tables() + return ret + return await _getTables(None,dbname) + + async def getTableFields(self,dbname,tblname): + @self.inSqlor + async def _getTableFields(sor,dbname,tblname): + ret = await sor.fields(tblname) + return ret + return await _getTableFields(None,dbname,tblname) + + async def getTablePrimaryKey(self,dbname,tblname): + @self.inSqlor + async def _getTablePrimaryKey(sor,dbname,tblname): + ret = await sor.primary(tblname) + return ret + return await _getTablePrimaryKey(None,dbname,tblname) + + async def getTableForignKeys(self,dbname,tblname): + @self.inSqlor + async def _getTableForignKeys(sor,dbname,tblname): + ret = await sor.fkeys(tblname) + return ret + return await _getTableForignKeys(None,dbname,tblname) + diff --git a/sqlor/ddl_template_mysql.py b/sqlor/ddl_template_mysql.py new file mode 100644 index 0000000..5c9b039 --- /dev/null +++ b/sqlor/ddl_template_mysql.py @@ -0,0 +1,55 @@ +mysql_ddl_tmpl = """{% macro typeStr(type,len,dec) %} +{%- if type=='str' -%} +VARCHAR({{len}}) +{%- elif type=='char' -%} +CHAR({{len}}) +{%- elif type=='long' or type=='int' or type=='short' -%} +int +{%- elif type=='long' -%} +bigint +{%- elif type=='float' or type=='double' or type=='ddouble' -%} +double({{len}},{{dec}}) +{%- elif type=='date' -%} +date +{%- elif type=='time' -%} +time +{%- elif type=='datetime' -%} +datetime +{%- elif type=='timestamp' -%} +TIMESTAMP +{%- elif type=='text' -%} +longtext +{%- elif type=='bin' -%} +longblob +{%- else -%} +{{type}} +{%- endif %} +{%- endmacro %} +{% macro nullStr(nullable) %} +{%- if nullable=='no' -%} +NOT NULL +{%- endif -%} +{% endmacro %} +{% macro primary() %} +{% if len(','.join(summary[0].primary))>0 %} +,primary key({{','.join(summary[0].primary)}}) +{% endif %} +{% endmacro %} +drop table {{summary[0].name}}; +CREATE TABLE {{summary[0].name}} +( +{% for field in fields %} + {{field.name}} {{typeStr(field.type,field.length,field.dec)}} {{nullStr(field.nullable)}} {%if field.title -%} comment '{{field.title}}'{%- endif %}{%- if not loop.last -%},{%- endif -%} +{% endfor %} +{{primary()}} +) +engine=innodb +default charset=utf8 +{% if summary[0].title %}comment '{{summary[0].title}}'{% endif %} +; +{% for v in validation %} +{% if v.oper=='idx' %} +CREATE {% if v.value.idxtype=='unique' %}UNIQUE{% endif %} INDEX {{summary[0].name}}_{{v.name}} ON {{summary[0].name}}({{",".join(v.value.fields)}}); +{% endif %} +{%- endfor -%} +""" \ No newline at end of file diff --git a/sqlor/ddl_template_oracle.py b/sqlor/ddl_template_oracle.py new file mode 100644 index 0000000..ff39372 --- /dev/null +++ b/sqlor/ddl_template_oracle.py @@ -0,0 +1,49 @@ +oracle_ddl_tmpl = """{% macro typeStr(type,len,dec) %} +{%- if type=='str' -%} +VARCHAR2({{len}}) +{%- elif type=='char' -%} +CHAR({{len}}) +{%- elif type=='long' or type=='int' or type=='short' -%} +NUMBER +{%- elif type=='float' or type=='double' or type=='ddouble' -%} +NUMBER({{len}},{{dec}}) +{%- elif type=='date' or type=='time' -%} +DATE +{%- elif type=='timestamp' -%} +TIMESTAMP +{%- elif type=='text' -%} +CLOB +{%- elif type=='bin' -%} +BLOB +{%- else -%} +{{type}} +{%- endif %} +{%- endmacro %} +{% macro nullStr(nullable) %} +{%- if nullable=='no' -%} +NOT NULL +{%- endif -%} +{% endmacro %} +{% macro primary() %} +{% if len(','.join(summary[0].primary))>0 %} +,primary key({{','.join(summary[0].primary)}}) +{% endif %} +{% endmacro %} +drop table {{summary[0].name}}; +CREATE TABLE {{summary[0].name}} +( +{% for field in fields %} + {{field.name}} {{typeStr(field.type,field.length,field.dec)}} {{nullStr(field.nullable)}}{%- if not loop.last -%},{%- endif -%} +{% endfor %} +{{primary()}} +); +{% for v in validation %} +{% if v.oper=='idx' %} +CREATE {% if v.value.idxtype=='unique' %}UNIQUE{% endif %} INDEX {{summary[0].name}}_{{v.name}} ON {{summary[0].name}}({{",".join(v.value.fields)}}); +{% endif %} +{%- endfor -%} +COMMENT ON TABLE {{summary[0].name}} IS '{{summary[0].title}}'; +{% for field in fields %} +COMMENT ON COLUMN {{summary[0].name}}.{{field.name}} is '{{field.title}}'; +{% endfor %} +""" \ No newline at end of file diff --git a/sqlor/ddl_template_postgresql.py b/sqlor/ddl_template_postgresql.py new file mode 100644 index 0000000..6b17592 --- /dev/null +++ b/sqlor/ddl_template_postgresql.py @@ -0,0 +1,45 @@ +postgresql_ddl_tmpl = """{% macro typeStr(type,len,dec) %} +{%- if type=='str' -%} +VARCHAR({{len}}) +{%- elif type=='char' -%} +CHAR({{len}}) +{%- elif type=='long' or type=='int' or type=='short' -%} +INTEGER +{%- elif type=='float' or type=='double' or type=='ddouble' -%} +NUMERIC({{len}},{{dec}}) +{%- elif type=='date' -%} +DATE +{%- elif type=='time' -%} +TIME +{%- elif type=='timestamp' -%} +TIMESTAMP +{%- else -%} +{{type}} +{%- endif %} +{%- endmacro %} +{% macro nullStr(nullable) %} +{%- if nullable=='no' -%} +NOT NULL +{%- endif -%} +{% endmacro %} +{% macro primary() %} +primary key({{','.join(summary[0].primary)}}) +{% endmacro %} +DROP TABLE IF EXISTS {{summary[0].name}}; +CREATE TABLE {{summary[0].name}} +( +{% for field in fields %} + {{field.name}} {{typeStr(field.type,field.length,field.dec)}} {{nullStr(field.nullable)}}, +{% endfor %} +{{primary()}} +); +{% for v in validation %} +{% if v.oper=='idx' %} +CREATE {% if v.value.idxtype=='unique' %}UNIQUE{% endif %} INDEX {{summary[0].name}}_{{v.name}} ON {{summary[0].name}}({{",".join(v.value.fields)}}); +{% endif %} +{%- endfor -%} +COMMENT ON TABLE {{summary[0].name}} IS '{{summary[0].title.decode('utf8')}}'; +{% for field in fields %} +COMMENT ON COLUMN {{summary[0].name}}.{{field.name}} is '{{field.title.decode('utf8')}}'; +{% endfor %} +""" \ No newline at end of file diff --git a/sqlor/ddl_template_sqlserver.py b/sqlor/ddl_template_sqlserver.py new file mode 100644 index 0000000..49e73c7 --- /dev/null +++ b/sqlor/ddl_template_sqlserver.py @@ -0,0 +1,51 @@ +sqlserver_ddl_tmpl = """{% macro typeStr(type,len,dec) %} +{%- if type=='str' -%} +NVARCHAR({{len}}) +{%- elif type=='char' -%} +CHAR({{len}}) +{%- elif type=='long' or type=='int' or type=='short' -%} +NUMERIC +{%- elif type=='float' or type=='double' or type=='ddouble' -%} +numeric({{len}},{{dec}}) +{%- elif type=='date' or type=='time' -%} +DATE +{%- elif type=='timestamp' -%} +TIMESTAMP +{%- elif type=='text' -%} +NVARCHAR(MAX) +{%- elif type=='bin' -%} +IMAGE +{%- else -%} +{{type}} +{%- endif %} +{%- endmacro %} +{% macro nullStr(nullable) %} +{%- if nullable=='no' -%} +NOT NULL +{%- endif -%} +{% endmacro %} + +{% macro primary() %} +{% if len(','.join(summary[0].primary))>0 %} +,primary key({{','.join(summary[0].primary)}}) +{% endif %} +{% endmacro %} + +drop table dbo.{{summary[0].name}}; +CREATE TABLE dbo.{{summary[0].name}} +( +{% for field in fields %} + {{field.name}} {{typeStr(field.type,field.length,field.dec)}} {{nullStr(field.nullable)}}{%- if not loop.last -%},{%- endif -%} +{% endfor %} +{{primary()}} +) +{% for v in validation %} +{% if v.oper=='idx' %} +CREATE {% if v.value.idxtype=='unique' %}UNIQUE{% endif %} INDEX {{summary[0].name}}_{{v.name}} ON {{summary[0].name}}({{",".join(v.value.fields)}}); +{% endif %} +{%- endfor -%} +EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'{{summary[0].title}}' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'{{summary[0].name}}' +{% for field in fields %} +EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'{{field.title}}' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'{{summary[0].name}}', @level2type=N'COLUMN',@level2name=N'{{field.name}}' +{% endfor %} +""" \ No newline at end of file diff --git a/sqlor/mssqlor.py b/sqlor/mssqlor.py new file mode 100644 index 0000000..b527a1a --- /dev/null +++ b/sqlor/mssqlor.py @@ -0,0 +1,175 @@ +# -*- coding:utf8 -*- +from .sor import SQLor +from .ddl_template_sqlserver import sqlserver_ddl_tmpl + +class MsSqlor(SQLor): + ddl_template = sqlserver_ddl_tmpl + db2modelTypeMapping = { + 'bit':'short', + 'tinyint':'short', + 'date':'date', + 'bigint':'long', + 'smallint':'short', + 'int':'long', + 'decimal':'float', + 'numeric':'float', + 'smallmoney':'float', + 'money':'float', + 'real':'float', + 'float':'float', + 'datetime':'date', + 'timestamp':'timestamp', + 'uniqueidentifier':'timestamp', + 'char':'char', + 'varchar':'str', + 'text':'text', + 'nchar':'str', + 'nvarchar':'str', + 'ntext':'text', + 'binary':'str', + 'varbinary':'str', + 'image':'file', + } + model2dbTypemapping = { + 'date':'datetime', + 'time':'date', + 'timestamp':'timestamp', + 'str':'nvarchar', + 'char':'char', + 'short':'int', + 'long':'numeric', + 'float':'numeric', + 'text':'ntext', + 'file':'image', + } + @classmethod + def isMe(self,name): + return name=='pymssql' + + def grammar(self): + return { + 'select':select_stmt, + } + + def placeHolder(self,varname): + if varname=='__mainsql__' : + return '' + return '%s' + + def dataConvert(self,dataList): + if type(dataList) == type({}): + d = [ i for i in dataList.values()] + else: + d = [ i['value'] for i in dataList] + return tuple(d) + + def pagingSQLmodel(self): + return u"""select * +from ( + select row_number() over(order by $[sort]$ $[order]$) as _row_id,page_s.* + from (%s) page_s + ) A +where _row_id >= $[from_line]$ and _row_id < $[end_line]$""" + + def tablesSQL(self): + sqlcmd = u"""select + lower(d.name) as name, + lower(cast(Isnull(f.VALUE,d.name) as nvarchar )) title + from sysobjects d + left join sys.extended_properties f on d.id = f.major_id and f.minor_id = 0 + where d.xtype = 'U'""" + return sqlcmd + + def fieldsSQL(self,tablename=None): + sqlcmd=u"""SELECT name = lower(a.name) + ,type = b.name + ,length = Columnproperty(a.id,a.name,'PRECISION') + ,dec = Isnull(Columnproperty(a.id,a.name,'Scale'),null) + ,nullable = CASE + WHEN a.isnullable = 1 THEN 'yes' + ELSE 'no' + END + ,title = lower(cast(Isnull(g.[value],a.name) as nvarchar) ) + ,table_name = lower(d.name) + FROM syscolumns a + LEFT JOIN systypes b + ON a.xusertype = b.xusertype + INNER JOIN sysobjects d + ON (a.id = d.id) + AND (d.xtype = 'U') + AND (d.name <> 'dtproperties') + INNER JOIN sys.all_objects c + ON d.id=c.object_id + AND schema_name(schema_id)='dbo' + LEFT JOIN sys.extended_properties g + ON (a.id = g.major_id) + AND (a.colid = g.minor_id) + LEFT JOIN sys.extended_properties f + ON (d.id = f.major_id) + AND (f.minor_id = 0)""" + if tablename is not None: + sqlcmd = sqlcmd + """ where lower(d.name)='%s' + ORDER BY a.id,a.colorder""" % tablename.lower() + else: + sqlcmd = sqlcmd + """ ORDER BY a.id,a.colorder""" + return sqlcmd + + def fkSQL(self,tablename=None): + sqlcmd = u"""select + MainCol.name AS field -- [主表列名] + ,oSub.name AS fk_table -- [子表名称], + ,SubCol.name AS fk_field -- [子表列名], +from + sys.foreign_keys fk + JOIN sys.all_objects oSub + ON (fk.parent_object_id = oSub.object_id) + JOIN sys.all_objects oMain + ON (fk.referenced_object_id = oMain.object_id) + JOIN sys.foreign_key_columns fkCols + ON (fk.object_id = fkCols.constraint_object_id) + JOIN sys.columns SubCol + ON (oSub.object_id = SubCol.object_id + AND fkCols.parent_column_id = SubCol.column_id) + JOIN sys.columns MainCol + ON (oMain.object_id = MainCol.object_id + AND fkCols.referenced_column_id = MainCol.column_id)""" + if tablename is not None: + sqlcmd = sqlcmd + """ where lower(oMain.name) = '%s'""" % tablename.lower() + + return sqlcmd + + def pkSQL(self,tablename=None): + sqlcmd = u"""select + lower(a.table_name) as table_name, + lower(b.column_name) as field_name + from information_schema.table_constraints a + inner join information_schema.constraint_column_usage b + on a.constraint_name = b.constraint_name + where a.constraint_type = 'PRIMARY KEY'""" + if tablename is not None: + sqlcmd = sqlcmd + """ and lower(a.table_name) = '%s'""" % tablename.lower() + return sqlcmd + + def indexesSQL(self,tablename=None): + sqlcmd = """SELECT +index_name=lower(IDX.Name), +index_type=case when KC.type_desc is null then 'primary' WHEN IDX.is_unique=1 THEN 'unique' ELSE 'ununique' END, +table_name=lower(O.Name), +column_name=lower(C.Name) +FROM sys.indexes IDX +INNER JOIN sys.index_columns IDXC +ON IDX.[object_id]=IDXC.[object_id] +AND IDX.index_id=IDXC.index_id +LEFT JOIN sys.key_constraints KC +ON IDX.[object_id]=KC.[parent_object_id] +AND IDX.index_id=KC.unique_index_id +INNER JOIN sys.objects O +ON O.[object_id]=IDX.[object_id] +INNER JOIN sys.columns C +ON O.[object_id]=C.[object_id] +AND O.type='U' +AND O.is_ms_shipped=0 +AND IDXC.Column_id=C.Column_id""" + if tablename is not None: + sqlcmd = sqlcmd + """ where lower(O.name)='%s'""" % tablename.lower() + return sqlcmd diff --git a/sqlor/mysqlor.py b/sqlor/mysqlor.py new file mode 100644 index 0000000..e5df373 --- /dev/null +++ b/sqlor/mysqlor.py @@ -0,0 +1,179 @@ +# -*- coding:utf8 -*- +from mysql import connector +from appPublic.argsConvert import ArgsConvert,ConditionConvert + +from .sor import SQLor +from .ddl_template_mysql import mysql_ddl_tmpl +class MySqlor(SQLor): + ddl_template = mysql_ddl_tmpl + db2modelTypeMapping = { + 'tinyint':'short', + 'smallint':'short', + 'mediumint':'long', + 'int':'long', + 'bigint':'long', + 'decimal':'float', + 'double':'float', + 'float':'float', + 'char':'char', + 'varchar':'str', + 'tinyblob':'text', + 'tinytext':'text', + 'mediumblob':'text', + 'mediumtext':'text', + 'blob':'text', + 'text':'text', + 'mediumblob':'text', + 'mediumtext':'text', + 'longblob':'bin', + 'longtext':'text', + 'barbinary':'text', + 'binary':'text', + 'date':'date', + 'time':'time', + 'datetime':'datetime', + 'timestamp':'datestamp', + 'year':'short', + } + model2dbTypemapping = { + 'date':'date', + 'time':'date', + 'timestamp':'timestamp', + 'str':'varchar', + 'char':'char', + 'short':'int', + 'long':'bigint', + 'float':'double', + 'text':'longtext', + 'bin':'longblob', + 'file':'longblob', + } + @classmethod + def isMe(self,name): + return name=='mysql.connector' + + def grammar(self): + return { + 'select':select_stmt, + } + + def _opendb(self): + self.conn = connector.connect(**self.dbdesc['kwargs']) + + def placeHolder(self,varname): + if varname=='__mainsql__' : + return '' + return '%s' + + def dataConvert(self,dataList): + if type(dataList) == type({}): + d = [ i for i in dataList.values()] + else: + d = [ i['value'] for i in dataList] + return tuple(d) + + def pagingSQL(self,sql,paging,NS): + """ + default it not support paging + """ + page = int(NS.get(paging['pagename'],1)) + rows = int(NS.get(paging['rowsname'],10)) + sort = NS.get(paging.get('sortname','sort'),None) + order = NS.get(paging.get('ordername','asc'),'asc') + if not sort: + return sql + if page < 1: + page = 1 + from_line = (page - 1) * rows + end_line = page * rows + 1 + psql = self.pagingSQLmodel() + ns={ + 'from_line':from_line, + 'end_line':end_line, + 'rows':rows, + 'sort':sort, + 'order':order, + } + ac = ArgsConvert('$[',']$') + psql = ac.convert(psql,ns) + retSQL=psql % sql + return retSQL + + def pagingSQLmodel(self): + return u"""select * from (%s) A order by $[sort]$ $[order]$ +limit $[from_line]$,$[rows]$""" + + def tablesSQL(self): + sqlcmd = """SELECT lower(TABLE_NAME) as name, lower(TABLE_COMMENT) as title FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '%s'""" % self.dbdesc.get('dbname','unknown') + return sqlcmd + + def fieldsSQL(self,tablename=None): + sqlcmd=""" + select + lower(column_name) as name, + data_type as type, + case when character_maximum_length is null then NUMERIC_PRECISION + else character_maximum_length end + as length, + NUMERIC_SCALE as dec1, + lower(is_nullable) as nullable, + column_comment as title, + lower(table_name) as table_name + from information_schema.columns where lower(TABLE_SCHEMA) = '%s' """ % self.dbdesc.get('dbname','unknown').lower() + if tablename is not None: + sqlcmd = sqlcmd + """and lower(table_name)='%s';""" % tablename.lower() + return sqlcmd + + def fkSQL(self,tablename=None): + sqlcmd = """SELECT C.TABLE_SCHEMA 拥有者, + C.REFERENCED_TABLE_NAME 父表名称 , + C.REFERENCED_COLUMN_NAME 父表字段 , + C.TABLE_NAME 子表名称, + C.COLUMN_NAME 子表字段, + C.CONSTRAINT_NAME 约束名, + T.TABLE_COMMENT 表注释, + R.UPDATE_RULE 约束更新规则, + R.DELETE_RULE 约束删除规则 + FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE C + JOIN INFORMATION_SCHEMA. TABLES T + ON T.TABLE_NAME = C.TABLE_NAME + JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS R + ON R.TABLE_NAME = C.TABLE_NAME + AND R.CONSTRAINT_NAME = C.CONSTRAINT_NAME + AND R.REFERENCED_TABLE_NAME = C.REFERENCED_TABLE_NAME + WHERE C.REFERENCED_TABLE_NAME IS NOT NULL ; + and C.TABLE_SCHEMA = '%s' +""" % self.dbdesc.get('dbname','unknown').lower() + if tablename is not None: + sqlcmd = sqlcmd + " and C.REFERENCED_TABLE_NAME = '%s'" % tablename.lower() + return sqlcmd + + def pkSQL(self,tablename=None): + sqlcmd = """SELECT + lower(c.table_name) as table_name, + lower(c.COLUMN_NAME) as field_name +FROM + INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS t, + INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS c +WHERE + t.CONSTRAINT_TYPE = 'PRIMARY KEY' + AND t.TABLE_SCHEMA = '%s' + AND t.TABLE_NAME = c.TABLE_NAME +""" % self.dbdesc.get('dbname','unknown').lower() + if tablename is not None: + sqlcmd = sqlcmd + " AND c.TABLE_NAME = '%s'" % tablename.lower() + return sqlcmd + + def indexesSQL(self,tablename=None): + sqlcmd = """SELECT DISTINCT + lower(index_name) as index_name, + lower(index_type) as index_type, + lower(table_name) as table_name, + lower(column_name) as column_name +FROM + information_schema.statistics +WHERE + table_schema = '%s'""" % self.dbdesc.get('dbname','unknown') + if tablename is not None: + sqlcmd = sqlcmd + """ AND table_name = '%s'""" % tablename.lower() + return sqlcmd diff --git a/sqlor/oracleor.py b/sqlor/oracleor.py new file mode 100644 index 0000000..75ca325 --- /dev/null +++ b/sqlor/oracleor.py @@ -0,0 +1,131 @@ +from .sor import SQLor +from .ddl_template_oracle import oracle_ddl_tmpl +class Oracleor(SQLor): + ddl_template = oracle_ddl_tmpl + db2modelTypeMapping = { + 'char':'char', + 'nchar':'str', + 'varchar':'str', + 'varchar2':'str', + 'nvarchar2':'str', + 'number':'long', + 'integer':'long', + 'binary_float':'float', + 'binary_double':'float', + 'float':'float', + 'timestamp':'timestamp', + 'timestamp with time zone':'timestamp', + 'timestamp with local time zone':'timestamp', + 'interval year to moth':'date', + 'interval day to second':'timestamp', + 'clob':'text', + 'nclob':'text', + 'blob':'file', + 'bfile':'file', + 'date':'date', + } + model2dbTypemapping = { + 'date':'date', + 'time':'date', + 'timestamp':'date', + 'str':'varchar2', + 'char':'char', + 'short':'number', + 'long':'number', + 'float':'number', + 'text':'nclob', + 'file':'blob', + } + @classmethod + def isMe(self,name): + return name=='cx_Oracle' + + def grammar(self): + return { + 'select':select_stmt, + } + + def placeHolder(self,varname): + if varname=='__mainsql__' : + return '' + return ':%s' % varname + + def dataConvert(self,dataList): + if type(dataList) == type({}): + return dataList + d = {} + [ d.update({i['name']:i['value']}) for i in dataList ] + return d + + def pagingSQLmodel(self): + return u"""select * +from ( + select page_s.*,rownum row_id + from (%s) page_s + order by $[sort]$ $[order]$ + ) +where row_id >=$[from_line]$ and row_id < $[end_line]$""" + + def tablesSQL(self): + sqlcmd = """select +lower(table_name) as name, +lower(decode(comments,null,table_name,comments)) as title +from USER_TAB_COMMENTS where table_type = 'TABLE'""" + return sqlcmd + + def fieldsSQL(self,tablename=None): + sqlcmd="""select lower(utc.COLUMN_NAME) name + ,utc.DATA_TYPE type + ,utc.DATA_LENGTH length + ,utc.data_scale dec + ,case when utc.nullable = 'Y' then 'yes' else 'no' end nullable + ,lower(nvl(ucc.comments,utc.COLUMN_NAME)) title + ,lower(utc.table_name) as table_name + from user_tab_cols utc left join USER_COL_COMMENTS ucc on utc.table_name = ucc.table_name and utc.COLUMN_NAME = ucc.COLUMN_NAME""" + if tablename is not None: + sqlcmd = sqlcmd + """ where lower(utc.table_name) = '%s'""" % tablename.lower() + return sqlcmd + + def fkSQL(self,tablename=None): + tablename = tablename.lower() + sqlcmd = """select + distinct(ucc.column_name) as field,rela.table_name as fk_table,rela.column_name as fk_field +from + user_constraints uc,user_cons_columns ucc, + ( + select t2.table_name,t2.column_name,t1.r_constraint_name + from user_constraints t1,user_cons_columns t2 + where t1.r_constraint_name=t2.constraint_name +) rela +where + uc.constraint_name=ucc.constraint_name + and uc.r_constraint_name=rela.r_constraint_name""" + if tablename is not None: + sqlcmd = sqlcmd + """ and lower(uc.table_name)='%s'""" % tablename.lower() + return sqlcmd + + def pkSQL(self,tablename=None): + sqlcmd = """ +select + lower(col.table_name) table_name, + lower(col.column_name) as field_name +from + user_constraints con,user_cons_columns col +where + con.constraint_name=col.constraint_name and con.constraint_type='P'""" + if tablename is not None: + sqlcmd = sqlcmd + """ and lower(col.table_name)='%s'""" % tablename.lower() + return sqlcmd + + def indexesSQL(self,tablename=None): + sqlcmd = """select + lower(a.index_name) index_name, + lower(a.UNIQUENESS) index_type, + lower(a.table_name) table_name, + lower(b.column_name) column_name +from user_indexes a, user_ind_columns b +where a.index_name = b.index_name""" + if tablename is not None: + sqlcmd += """ and lower(a.table_name) = lower('%s')""" % tablename.lower() + return sqlcmd + diff --git a/sqlor/postgresqlor.py b/sqlor/postgresqlor.py new file mode 100644 index 0000000..0fd3033 --- /dev/null +++ b/sqlor/postgresqlor.py @@ -0,0 +1,199 @@ +from .sor import SQLor +from .ddl_template_postgresql import postgresql_ddl_tmpl + +class PostgreSQLor(SQLor): + ddl_template = postgresql_ddl_tmpl + db2modelTypeMapping = { + 'smallint':'short', + 'integer':'long', + 'bigint':'llong', + 'decimal':'float', + 'numeric':'float', + 'real':'float', + 'double':'float', + 'serial':'long', + 'bigserial':'llong', + 'char':'char', + 'character':'char', + 'varchar':'str', + 'character varying':'str', + 'text':'text', + 'timestamp':'timestamp', + 'date':'date', + 'time':'time', + 'boolean':'char', + 'bytea':'file' + } + model2dbTypemapping = { + 'date':'date', + 'time':'date', + 'timestamp':'timestamp', + 'str':'varchar', + 'char':'char', + 'short':'smallint', + 'long':'integer', + 'float':'numeric', + 'text':'text', + 'file':'bytea', + } + @classmethod + def isMe(self,name): + return name=='psycopg2' + + def grammar(self): + return { + 'select':select_stmt, + } + + def placeHolder(self,varname): + if varname=='__mainsql__' : + return '' + return ':%s' % varname + + def dataConvert(self,dataList): + if type(dataList) == type({}): + return dataList + d = {} + [ d.update({i['name']:i['value']}) for i in dataList ] + return d + + def pagingSQLmodel(self): + return u"""select * +from ( + select page_s.*,rownum row_id + from (%s) page_s + order by $[sort]$ $[order]$ + ) +where row_id >=$[from_line]$ and row_id < $[end_line]$""" + + def tablesSQL(self): + """ + 列出表名 + SELECT tablename FROM pg_tables; + WHERE tablename NOT LIKE 'pg%' + AND tablename NOT LIKE 'sql_%' + ORDER BY tablename; + """ + sqlcmd = """select +lower(table_name) as name, +lower(decode(comments,null,table_name,comments)) as title +from USER_TAB_COMMENTS where table_type = 'TABLE'""" + return sqlcmd + + def fieldsSQL(self,tablename=None): + """SELECT col_description(a.attrelid,a.attnum) as comment,pg_type.typname as typename,a.attname as name, a.attnotnull as notnull +FROM pg_class as c,pg_attribute as a inner join pg_type on pg_type.oid = a.atttypid +where c.relname = 'tablename' and a.attrelid = c.oid and a.attnum>0 + """ + sqlcmd="""select lower(utc.COLUMN_NAME) name + ,utc.DATA_TYPE type + ,utc.DATA_LENGTH length + ,utc.data_scale dec + ,case when utc.nullable = 'Y' then 'yes' else 'no' end nullable + ,lower(nvl(ucc.comments,utc.COLUMN_NAME)) title + ,lower(utc.table_name) as table_name + from user_tab_cols utc left join USER_COL_COMMENTS ucc on utc.table_name = ucc.table_name and utc.COLUMN_NAME = ucc.COLUMN_NAME""" + if tablename is not None: + sqlcmd = sqlcmd + """ where lower(utc.table_name) = '%s'""" % tablename.lower() + return sqlcmd + + def fkSQL(self,tablename=None): + tablename = tablename.lower() + sqlcmd = """select + distinct(ucc.column_name) as field,rela.table_name as fk_table,rela.column_name as fk_field +from + user_constraints uc,user_cons_columns ucc, + ( + select t2.table_name,t2.column_name,t1.r_constraint_name + from user_constraints t1,user_cons_columns t2 + where t1.r_constraint_name=t2.constraint_name +) rela +where + uc.constraint_name=ucc.constraint_name + and uc.r_constraint_name=rela.r_constraint_name""" + if tablename is not None: + sqlcmd = sqlcmd + """ and lower(uc.table_name)='%s'""" % tablename.lower() + return sqlcmd + + def pkSQL(self,tablename=None): + """ + select pg_attribute.attname as colname,pg_type.typname as typename,pg_constraint.conname as pk_name from +pg_constraint inner join pg_class +on pg_constraint.conrelid = pg_class.oid +inner join pg_attribute on pg_attribute.attrelid = pg_class.oid +and pg_attribute.attnum = pg_constraint.conkey[1] +inner join pg_type on pg_type.oid = pg_attribute.atttypid +where pg_class.relname = 'tablename' +and pg_constraint.contype='p' + """ + sqlcmd = """ +select + lower(col.table_name) table_name, + lower(col.column_name) as field_name +from + user_constraints con,user_cons_columns col +where + con.constraint_name=col.constraint_name and con.constraint_type='P'""" + if tablename is not None: + sqlcmd = sqlcmd + """ and lower(col.table_name)='%s'""" % tablename.lower() + return sqlcmd + + def indexesSQL(self,tablename=None): + """ +SELECT + +A.SCHEMANAME, + +A.TABLENAME, + +A.INDEXNAME, + +A.TABLESPACE, + +A.INDEXDEF, + +B.AMNAME, + +C.INDEXRELID, + +C.INDNATTS, + +C.INDISUNIQUE, + +C.INDISPRIMARY, + +C.INDISCLUSTERED, + +D.DESCRIPTION + +FROM + +PG_AM B + +LEFT JOIN PG_CLASS F ON B.OID = F.RELAM + +LEFT JOIN PG_STAT_ALL_INDEXES E ON F.OID = E.INDEXRELID + +LEFT JOIN PG_INDEX C ON E.INDEXRELID = C.INDEXRELID + +LEFT OUTER JOIN PG_DESCRIPTION D ON C.INDEXRELID = D.OBJOID, + +PG_INDEXES A + +WHERE + +A.SCHEMANAME = E.SCHEMANAME AND A.TABLENAME = E.RELNAME AND A.INDEXNAME = E.INDEXRELNAME + +AND E.SCHEMANAME = 'public' AND E.RELNAME = 'table_name' +""" + sqlcmd = """select + lower(a.index_name) index_name, + lower(a.UNIQUENESS) index_type, + lower(a.table_name) table_name, + lower(b.column_name) column_name +from user_indexes a, user_ind_columns b +where a.index_name = b.index_name""" + if tablename is not None: + sqlcmd += """ and lower(a.table_name) = lower('%s')""" % tablename.lower() + return sqlcmd + diff --git a/sqlor/sor.py b/sqlor/sor.py new file mode 100644 index 0000000..b3a18b4 --- /dev/null +++ b/sqlor/sor.py @@ -0,0 +1,454 @@ +import os +os.environ['NLS_LANG'] = 'SIMPLIFIED CHINESE_CHINA.UTF8' +import sys +import codecs +import re +import json +from appPublic.myImport import myImport +from appPublic.dictObject import DictObject,dictObjectFactory +from appPublic.unicoding import uDict +from patterncoding.myTemplateEngine import MyTemplateEngine + + +from appPublic.argsConvert import ArgsConvert,ConditionConvert + +class SQLorException(Exception,object): + def __int__(self,**kvs): + supper(SQLException,self).__init__(self,**kvs) + self.dic = { + 'response':'error', + 'errtype':'SQLor', + 'errmsg':supper(SQLException,self).message, + } + + def __str__(self): + return 'errtype:%s,errmsg=%s' % (self.dic['errtype'],self.dic['errmsg']) + +def setValues(params,ns): + r = ns.get(params,os.getenv(params)) + return r + +def findNamedParameters(sql): + """ + return a list of named parameters + """ + re1 = '\$\{[_a-zA-Z_][a-zA-Z_0-9]*\}' + params1 = re.findall(re1,sql) + return params1 + + +def uniParams(params1): + ret = [] + for i in params1: + if i not in ret: + ret.append(i) + return ret + +def readsql(fn): + f = codecs.open(fn,'r','utf-8') + b = f.read() + f.close() + return b + +class SQLor(object): + def __init__(self,dbdesc=None,sqltp = '$[',sqlts = ']$',sqlvp = '${',sqlvs = '}$'): + self.conn = None + self.cur = None + self.async_mode = False + self.sqltp = sqltp + self.sqlts = sqlts + self.sqlvp = sqlvp + self.sqlvs = sqlvs + self.dbdesc = dbdesc + self.writer = None + self.convfuncs = {} + self.cc = ConditionConvert() + + def setCursor(self,async_mode,conn,cur): + self.async_mode = async_mode + self.conn = conn + self.cur = cur + + def getConn(self): + return self.conn + + def setConvertFunction(self,typ,func): + self.convfuncs.update({typ:func}) + + def convert(self,typ,value): + if self.convfuncs.get(typ,None) is not None: + return self.convfuncs[typ](value) + return value + @classmethod + def isMe(self,name): + return name=='sqlor' + + def pagingSQLmodel(self): + return u"" + + def placeHolder(self,varname): + if varname=='__mainsql__' : + return '' + return '?' + + def dataConvert(self,dataList): + return [ i.get('value',None) for i in dataList] + + def dataList(self,k,v): + a = [] + a.append({'name':k,'value':v}) + return a + + def cursor(self): + return self.cur + + def recordCnt(self,sql): + ret = u"""select count(*) rcnt from (%s) rowcount_table""" % sql + return ret + + def pagingSQL(self,sql,paging,NS): + """ + default it not support paging + """ + page = int(NS.get(paging['pagename'],1)) + rows = int(NS.get(paging['rowsname'],10)) + sort = NS.get(paging.get('sortname','sort'),None) + order = NS.get(paging.get('ordername','asc'),'asc') + if not sort: + return sql + if page < 1: + page = 1 + from_line = (page - 1) * rows + 1 + end_line = page * rows + 1 + psql = self.pagingSQLmodel() + ns={ + 'from_line':from_line, + 'end_line':end_line, + 'rows':rows, + 'sort':sort, + 'order':order, + } + ac = ArgsConvert('$[',']$') + psql = ac.convert(psql,ns) + retSQL=psql % sql + return retSQL + + def filterSQL(self,sql,filters,NS): + ac = ArgsConvert('$[',']$') + fbs = [] + for f in filters: + vars = ac.findAllVariables(f) + if len(vars) > 0: + ignoreIt = False + for v in vars: + if not NS.get(v,False): + ignoreIt = True + if not ignoreIt: + f = ac.convert(f,NS) + else: + f = '1=1' + fbs.append(f) + fb = ' '.join(fbs) + retsql = u"""select * from (%s) filter_table where %s""" % (sql,fb) + return retsql + + async def runVarSQL(self,cursor,sql,NS): + """ + using a opened cursor to run a SQL statment with variable, the variable is setup in NS namespace + return a cursor with data + """ + markedSQL,datas = self.maskingSQL(sql,NS) + datas = self.dataConvert(datas) + try: + markedSQL = markedSQL.encode('utf8') + if self.async_mode: + await cursor.execute(markedSQL,datas) + else: + cursor.execute(markedSQL,datas) + + except Exception as e: + print( "markedSQL=",markedSQL,datas,e) + raise e + return + + def maskingSQL(self,org_sql,NS): + """ + replace all ${X}$ format variable exception named by '__mainsql__' in sql with '%s', + and return the marked sql sentent and variable list + sql is a sql statment with variable formated in '${X}$ + the '__mainsql__' variable use to identify the main sql will outout data. + NS is the name space the variable looking for, it is a variable dictionary + return (MarkedSQL,list_of_variable) + """ + sqltextAC = ArgsConvert(self.sqltp,self.sqlts) + sqlargsAC = ArgsConvert(self.sqlvp,self.sqlvs) + sql1 = sqltextAC.convert(org_sql,NS) + cc = ConditionConvert() + sql1 = cc.convert(sql1,NS) + vars = sqlargsAC.findAllVariables(sql1) + phnamespace = {} + [phnamespace.update({v:self.placeHolder(v)}) for v in vars] + m_sql = sqlargsAC.convert(sql1,phnamespace) + newdata = [] + for v in vars: + if v != '__mainsql__': + value = sqlargsAC.getVarValue(v,NS,None) + newdata += self.dataList(v,value) + + return (m_sql,newdata) + + async def execute(self,sql,value,callback,**kwargs): + cur = self.cursor() + await self.runVarSQL(cur,sql,value) + if callback is not None: + fields = [ i[0].lower() for i in cur.description ] + if self.async_mode: + rec = await cur.fetchone() + else: + rec = cur.fetchone() + + while rec is not None: + dic = {} + for i in range(len(fields)): + dic.update({fields[i]:rec[i]}) + #dic = uDict(dic,coding='utf8') + callback(dic,**kwargs) + if self.async_mode: + rec = await cur.fetchone() + else: + rec = cur.fetchone() + + + async def executemany(self,sql,values): + cur = self.cursor() + markedSQL,datas = self.maskingSQL(sql,{}) + datas = [ self.dataConvert(d) for d in values ] + if async_mode: + await cur.executemany(markedSQL,datas) + else: + cur.executemany(markedSQL,datas) + + def pivotSQL(self,tablename,rowFields,columnFields,valueFields): + def maxValue(columnFields,valueFields,cfvalues): + sql = '' + for f in valueFields: + i = 0 + for field in columnFields: + for v in cfvalues[field]: + sql += """ + ,sum(%s_%d) %s_%d""" % (f,i,f,i) + i+=1 + return sql + def casewhen(columnFields,valueFields,cfvalues): + sql = '' + for f in valueFields: + i = 0 + for field in columnFields: + for v in cfvalues[field]: + if v is None: + sql += """,case when %s is null then %s + else 0 end as %s_%d -- %s + """ % (field,f,f,i,v) + else: + sql += """,case when trim(%s) = trim('%s') then %s + else 0 end as %s_%d -- %s + """ % (field,v,f,f,i,v) + + i += 1 + return sql + + cfvalues={} + for field in columnFields: + sqlstring = 'select distinct %s from %s' % (field,tablename) + v = [] + self.execute(sqlstring,{},lambda x: v.append(x)) + cfvalues[field] = [ i[field] for i in v ] + + sql =""" + select """ + ','.join(rowFields) + sql += maxValue(columnFields,valueFields,cfvalues) + sql += """ from + (select """ + ','.join(rowFields) + sql += casewhen(columnFields,valueFields,cfvalues) + sql += """ + from %s) + group by %s""" % (tablename,','.join(rowFields)) + return sql + + async def pivot(self,desc,tablename,rowFields,columnFields,valueFields): + sql = self.pivotSQL(tablename,rowFields,columnFields,valueFields) + desc['sql_string'] = sql + ret = [] + return await self.execute(sql,{},lambda x:ret.append(x)) + + def isSelectSql(self,sql): + i = 0 + while sql[i] in "\r\n \t": + i = i + 1 + return sql.lower().startswith('select ') + + def getSQLfromDesc(self,desc): + sql = '' + if 'sql_file' in desc.keys(): + sql = readsql(desc['sql_file']) + else: + sql = desc['sql_string'] + return sql + + async def record_count(self,desc,NS): + cnt_desc = {} + cnt_desc.update(desc) + sql = self.getSQLfromDesc(desc) + if desc.get('sql_file',False): + del cnt_desc['sql_file'] + cnt_desc['sql_string'] = self.recordCnt(sql) + class Cnt: + def __init__(self): + self.recs = [] + def handler(self,rec): + self.recs.append(rec) + + c = Cnt() + await self.sqlIterator(cnt_desc,NS,c.handler) + print(c.recs[0]) + t = c.recs[0]['rcnt'] + return t + + async def pagingdata(self,desc,NS): + paging_desc = {} + paging_desc.update(desc) + paging_desc.update( + { + "paging":{ + "rowsname":"rows", + "pagename":"page", + "sortname":"sort", + "ordername":"order" + } + }) + if desc.get('sortfield',False): + NS['sort'] = desc.get('sortfield') + sql = self.getSQLfromDesc(desc) + if desc.get('sql_file',False): + del cnt_desc['sql_file'] + paging_desc['sql_string'] = self.pagingSQL(sql, + paging_desc.get('paging'),NS) + + class Cnt: + def __init__(self): + self.recs = [] + def handler(self,rec): + self.recs.append(rec) + + c = Cnt() + await self.sqlIterator(paging_desc,NS,c.handler) + return c.recs + + async def runSQL(self,desc,NS,callback,**kw): + class RecordHandler: + def __init__(self,ns,name): + self.ns = ns + self.name = name + self.ns[name] = [] + + def handler(self,rec): + obj = DictObject(rec) + self.ns[self.name].append(obj) + + cur = self.cursor() + sql = self.getSQLfromDesc(desc) + if self.isSelectSql(sql): + if callback is None: + klass = desc.get('dataname','dummy') + if klass is not None: + rh = RecordHandler(NS,klass) + callback = rh.handler + else: + callback = None + await self.execute(sql,NS,callback) + + async def sqlExecute(self,desc,NS): + await self.execute(sql,NS,None) + + async def tables(self): + sqlstring = self.tablesSQL() + ret = [] + await self.execute(sqlstring,{},lambda x:ret.append(x)) + return ret + + def indexesSQL(self,tablename): + """ + record of { + index_name, + index_type, + table_name, + column_name + } + """ + return None + + async def indexes(self,tablename=None): + sqlstring = self.indexesSQL(tablename.lower()) + if sqlstring is None: + return [] + recs = [] + await self.execute(sqlstring,{},lambda x:recs.append(x)) + return recs + + async def fields(self,tablename=None): + sqlstring = self.fieldsSQL(tablename) + recs = [] + await self.execute(sqlstring,{},lambda x:recs.append(x)) + ret = [] + for r in recs: + r.update({'type':self.db2modelTypeMapping.get(r['type'].lower(),'unknown')}) + r.update({'name':r['name'].lower()}) + ret.append(r) + return ret + + async def primary(self,tablename): + sqlstring = self.pkSQL(tablename) + recs = [] + await self.execute(sqlstring,{},lambda x:recs.append(x)) + return recs + + async def fkeys(self,tablename): + sqlstring = self.fkSQL(tablename) + recs = [] + await self.execute(sqlstring,{},lambda x:recs.append(x)) + return recs + + async def createTable(self,tabledesc): + te = MyTemplateEngine([],'utf8','utf8') + desc = { + "sql_string":te.renders(self.ddl_template,tabledesc) + } + return await self.sqlExecute(desc,{}) + + async def getTableDesc(self,tablename): + desc = {} + summary = [ i for i in await self.tables() if tablename.lower() == i.name ] + primary = [i.field_name for i in await self.primary(tablename) ] + summary['primary'] = primary + desc['summary'] = summary + desc['fields'] = await self.fields(tablename=tablename) + desc['validation'] = [] + idx = {} + async for idxrec in self.indexes(tablename=tablename): + if idxrec.index_name != idx.get('name',None): + if idx != {}: + desc['validation'].append(idx) + idx = { + 'fields':[] + } + else: + idx['fields'] = [] + idx['name'] = idxrec.index_name + idx['oper'] = 'idx' + idx['fields'].append(idxrec.field_name) + if idx != {}: + desc['validation'].append(idx) + return desc + + + diff --git a/sqlor/sqlite3or.py b/sqlor/sqlite3or.py new file mode 100644 index 0000000..adb065d --- /dev/null +++ b/sqlor/sqlite3or.py @@ -0,0 +1,100 @@ +import re +from .sor import SQLor + +class SQLite3or(SQLor): + db2modelTypeMapping = { + 'char':'char', + 'nchar':'str', + 'text':'text', + 'ntext':'text', + 'varchar':'str', + 'nvarchar':'str', + 'blob':'file', + 'integer':'long', + 'double':'float', + 'date':'date', + 'time':'time', + 'timestamp':'timestamp', + 'number':'long', + } + model2dbTypemapping = { + 'date':'date', + 'time':'time', + 'timestamp':'timestamp', + 'str':'nvarchar', + 'char':'char', + 'short':'int', + 'long':'integer', + 'float':'double', + 'text':'ntext', + 'file':'blob', + } + @classmethod + def isMe(self,name): + return name=='sqlite3' + + def placeHolder(self,varname): + if varname=='__mainsql__' : + return '' + return '?' + + def dataConvert(self,dataList): + if type(dataList) == type({}): + d = [ i for i in dataList.values()] + else: + d = [ i['value'] for i in dataList] + return tuple(d) + + def pagingSQLmodel(self): + sql = u"""select * from (%s) order by $[sort]$ $[order]$ limit $[from_line]$,$[end_line]$""" + return sql + + def tablesSQL(self): + sqlcmd = u"""select name, tbl_name as title from sqlite_master where upper(type) = 'TABLE'""" + return sqlcmd + + def fieldsSQL(self,tablename): + sqlcmd="""PRAGMA table_info('%s')""" % tablename.lower() + return sqlcmd + + def fields(self,tablename): + m = u'(\w+)\(((\d+)(,(\d+)){0,1})\){0,1}' + k = re.compile(m) + def typesplit(typ): + d = k.search(typ) + if d is None: + return typ,0,0 + + return d.group(1),int(d.group(3) if d.group(3) is not None else 0 ),int(d.group(5) if d.group(5) is not None else 0) + + sqlstring = self.fieldsSQL(tablename) + recs = [] + self.execute(sqlstring,callback=lambda x:recs.append(x)) + for r in recs: + t,l,d = typesplit(r['type']) + r['type'] = t + r['length'] = int(l) + r['dec'] = int(d) + r['title'] = r['name'] + ret = [] + for r in recs: + r.update({'type':self.db2modelTypeMapping[r['type'].lower()]}) + r.update({'name':r['name'].lower()}) + ret.append(r) + return ret + + def fkSQL(self,tablename): + sqlcmd = "" + return sqlcmd + + def fkeys(self,tablename): + return [] + + def primary(self,tablename): + recs = self.fields(tablename) + ret = [ {'field':r['name']} for r in recs if r['pk'] == 1 ] + return ret + + def pkSQL(self,tablename): + sqlcmd = "" + return sqlcmd diff --git a/test/primary.py b/test/primary.py new file mode 100644 index 0000000..5c3c890 --- /dev/null +++ b/test/primary.py @@ -0,0 +1,47 @@ +import asyncio + +from sqlor.dbpools import DBPools + +dbs={ + "aiocfae":{ + "driver":"aiomysql", + "async_mode":True, + "coding":"utf8", + "dbname":"cfae", + "kwargs":{ + "user":"test", + "db":"cfae", + "password":"test123", + "host":"localhost" + } + }, + "cfae":{ + "driver":"mysql.connector", + "coding":"utf8", + "dbname":"cfae", + "kwargs":{ + "user":"test", + "db":"cfae", + "password":"test123", + "host":"localhost" + } + } +} + +loop = asyncio.get_event_loop() +pool = DBPools(dbs,loop=loop) +async def printTables(dbname): + r = await pool.getTables(dbname) + print('tables=',r) + +async def printFields(dbname,tablename): + r = await pool.getTableFields(dbname,tablename) + print(dbname,tablename,'fields=',r) + +async def printPrimary(dbname,tablename): + r = await pool.getTablePrimaryKey(dbname,tablename) + print(dbname,tablename,'primary key=',r) + +loop.run_until_complete(printTables('cfae')) +loop.run_until_complete(printFields('cfae','product')) +loop.run_until_complete(printPrimary('cfae','product')) diff --git a/test/t1.py b/test/t1.py new file mode 100644 index 0000000..15de10d --- /dev/null +++ b/test/t1.py @@ -0,0 +1,44 @@ +import asyncio + +from sqlor.dbpools import DBPools + +dbs={ + "aiocfae":{ + "driver":"aiomysql", + "async_mode":True, + "coding":"utf8", + "dbname":"cfae", + "kwargs":{ + "user":"test", + "db":"cfae", + "password":"test123", + "host":"localhost" + } + }, + "cfae":{ + "driver":"mysql.connector", + "coding":"utf8", + "dbname":"cfae", + "kwargs":{ + "user":"test", + "db":"cfae", + "password":"test123", + "host":"localhost" + } + } +} + +loop = asyncio.get_event_loop() +pool = DBPools(dbs,loop=loop) + +async def paging(): + @pool.runSQLPaging + def sql(db,ns): + return { + "sql_string":"select * from product", + } + x = await sql('aiocfae',{'rows':5,'page':1,"sort":"productid"}) + print('x=',x['total'],len(x['rows'])) + + +loop.run_until_complete(paging()) diff --git a/test/t2.py b/test/t2.py new file mode 100644 index 0000000..9a45d0a --- /dev/null +++ b/test/t2.py @@ -0,0 +1,42 @@ +import asyncio + +from sqlor.dbpools import DBPools + +dbs={ + "aiocfae":{ + "driver":"aiomysql", + "async_mode":True, + "coding":"utf8", + "dbname":"cfae", + "kwargs":{ + "user":"test", + "db":"cfae", + "password":"test123", + "host":"localhost" + } + }, + "cfae":{ + "driver":"mysql.connector", + "coding":"utf8", + "dbname":"cfae", + "kwargs":{ + "user":"test", + "db":"cfae", + "password":"test123", + "host":"localhost" + } + } +} + +loop = asyncio.get_event_loop() +pool = DBPools(dbs,loop=loop) + +async def testfunc(): + @pool.runSQL + def sql(db,ns,callback): + return { + "sql_string":"select * from product", + } + x = await sql('cfae',{},print) + +loop.run_until_complete(testfunc()) diff --git a/test/test.py b/test/test.py new file mode 100644 index 0000000..9a45d0a --- /dev/null +++ b/test/test.py @@ -0,0 +1,42 @@ +import asyncio + +from sqlor.dbpools import DBPools + +dbs={ + "aiocfae":{ + "driver":"aiomysql", + "async_mode":True, + "coding":"utf8", + "dbname":"cfae", + "kwargs":{ + "user":"test", + "db":"cfae", + "password":"test123", + "host":"localhost" + } + }, + "cfae":{ + "driver":"mysql.connector", + "coding":"utf8", + "dbname":"cfae", + "kwargs":{ + "user":"test", + "db":"cfae", + "password":"test123", + "host":"localhost" + } + } +} + +loop = asyncio.get_event_loop() +pool = DBPools(dbs,loop=loop) + +async def testfunc(): + @pool.runSQL + def sql(db,ns,callback): + return { + "sql_string":"select * from product", + } + x = await sql('cfae',{},print) + +loop.run_until_complete(testfunc())