From b6d3574f6509588396515a277b30f36c571a863e Mon Sep 17 00:00:00 2001 From: yumoqing Date: Tue, 14 Oct 2025 17:53:50 +0800 Subject: [PATCH] bugfix --- sqlor/clickhouseor.py | 97 ++++++++++++++++++++++++++++++++ sqlor/ddl_template_clickhouse.py | 56 ++++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100644 sqlor/clickhouseor.py create mode 100644 sqlor/ddl_template_clickhouse.py diff --git a/sqlor/clickhouseor.py b/sqlor/clickhouseor.py new file mode 100644 index 0000000..b5c0e9b --- /dev/null +++ b/sqlor/clickhouseor.py @@ -0,0 +1,97 @@ +# -*- coding:utf8 -*- +from appPublic.argsConvert import ArgsConvert, ConditionConvert +from .sor import SQLor +from .ddl_template_clickhouse import clickhouse_ddl_tmpl +from .const import ROWS + +class ClickHouseor(SQLor): + ddl_template = clickhouse_ddl_tmpl + + db2modelTypeMapping = { + 'int8': 'short', + 'int16': 'short', + 'int32': 'long', + 'int64': 'long', + 'float32': 'float', + 'float64': 'float', + 'decimal': 'float', + 'string': 'str', + 'date': 'date', + 'datetime': 'datetime', + 'uuid': 'str', + 'bool': 'short', + } + + model2dbTypemapping = { + 'short': 'Int32', + 'long': 'Int64', + 'float': 'Float64', + 'str': 'String', + 'char': 'String', + 'date': 'Date', + 'datetime': 'DateTime', + 'timestamp': 'DateTime', + 'text': 'String', + 'bin': 'String', + 'file': 'String', + } + + @classmethod + def isMe(self, name): + return name.lower() in ('clickhouse', 'clickhouse_driver') + + def placeHolder(self, varname, pos=None): + if varname == '__mainsql__': + return '' + return '%s' + + def dataConvert(self, dataList): + if isinstance(dataList, dict): + d = [i for i in dataList.values()] + else: + d = [i['value'] for i in dataList] + return tuple(d) + + def pagingSQLmodel(self): + # ClickHouse 支持 LIMIT offset, size + return """SELECT * FROM (%s) AS A LIMIT $[from_line]$, $[rows]$""" + + def tablesSQL(self): + return """SELECT name, comment AS title +FROM system.tables +WHERE database = '%s'""" % self.dbdesc.get('dbname', 'default') + + def fieldsSQL(self, tablename=None): + sql = """SELECT + name AS name, + type AS type, + NULL AS length, + NULL AS dec, + 'yes' AS nullable, + comment AS title, + '%s' AS table_name +FROM system.columns +WHERE database = '%s' +""" % (tablename or '', self.dbdesc.get('dbname', 'default')) + if tablename: + sql += " AND table = '%s';" % tablename + return sql + + def pkSQL(self, tablename=None): + # ClickHouse 没有 system.keys 表,用 order_by_keys 替代 + sql = """SELECT name FROM system.columns +WHERE database = '%s' AND table = '%s' AND is_in_primary_key = 1; +""" % (self.dbdesc.get('dbname', 'default'), tablename.lower()) + return sql + + def indexesSQL(self, tablename=None): + # ClickHouse 没有传统索引 + return """SELECT name, 'order_by' AS index_name, 'primary' AS is_unique, name AS column_name +FROM system.columns +WHERE database = '%s' AND table = '%s' AND is_in_primary_key = 1; +""" % (self.dbdesc.get('dbname', 'default'), tablename.lower()) + + def fkSQL(self, tablename=None): + # ClickHouse 不支持外键 + return "SELECT 'ClickHouse does not support foreign keys' AS msg;" + diff --git a/sqlor/ddl_template_clickhouse.py b/sqlor/ddl_template_clickhouse.py new file mode 100644 index 0000000..a7751d3 --- /dev/null +++ b/sqlor/ddl_template_clickhouse.py @@ -0,0 +1,56 @@ +clickhouse_ddl_tmpl = """{% macro typeStr(type,len,dec) %} +{%- if type in ['str', 'char', 'text'] -%} +String +{%- elif type in ['short', 'int'] -%} +Int32 +{%- elif type == 'long' -%} +Int64 +{%- elif type in ['float', 'double', 'ddouble'] -%} +Float64 +{%- elif type == 'date' -%} +Date +{%- elif type in ['datetime', 'timestamp'] -%} +DateTime +{%- elif type == 'bin' -%} +String +{%- else -%} +{{ type | upper }} +{%- endif %} +{%- endmacro %} + +{% macro defaultValue(defaultv) %} +{%- if defaultv %} DEFAULT '{{defaultv}}'{%- endif -%} +{%- endmacro %} + +{% macro nullStr(nullable) %} +{%- if nullable == 'no' -%} +NOT NULL +{%- else -%} +NULL +{%- endif -%} +{% endmacro %} + +{% macro primary() %} +PRIMARY KEY ({{ ','.join(summary[0].primary) }}) +ORDER BY ({{ ','.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) }} {{ defaultValue(field.default) }}{% if field.title %} COMMENT '{{field.title}}'{% endif %}{% if not loop.last %},{% endif %} +{% endfor %} +) +ENGINE = MergeTree() +{% if summary[0].primary and len(summary[0].primary) > 0 %} +{{ primary() }} +{% else %} +ORDER BY tuple() +{% endif %} +{% if summary[0].title %} +COMMENT '{{ summary[0].title }}' +{% endif %} +; +""" +