base.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741
  1. # firebird/base.py
  2. # Copyright (C) 2005-2017 the SQLAlchemy authors and contributors
  3. # <see AUTHORS file>
  4. #
  5. # This module is part of SQLAlchemy and is released under
  6. # the MIT License: http://www.opensource.org/licenses/mit-license.php
  7. r"""
  8. .. dialect:: firebird
  9. :name: Firebird
  10. Firebird Dialects
  11. -----------------
  12. Firebird offers two distinct dialects_ (not to be confused with a
  13. SQLAlchemy ``Dialect``):
  14. dialect 1
  15. This is the old syntax and behaviour, inherited from Interbase pre-6.0.
  16. dialect 3
  17. This is the newer and supported syntax, introduced in Interbase 6.0.
  18. The SQLAlchemy Firebird dialect detects these versions and
  19. adjusts its representation of SQL accordingly. However,
  20. support for dialect 1 is not well tested and probably has
  21. incompatibilities.
  22. Locking Behavior
  23. ----------------
  24. Firebird locks tables aggressively. For this reason, a DROP TABLE may
  25. hang until other transactions are released. SQLAlchemy does its best
  26. to release transactions as quickly as possible. The most common cause
  27. of hanging transactions is a non-fully consumed result set, i.e.::
  28. result = engine.execute("select * from table")
  29. row = result.fetchone()
  30. return
  31. Where above, the ``ResultProxy`` has not been fully consumed. The
  32. connection will be returned to the pool and the transactional state
  33. rolled back once the Python garbage collector reclaims the objects
  34. which hold onto the connection, which often occurs asynchronously.
  35. The above use case can be alleviated by calling ``first()`` on the
  36. ``ResultProxy`` which will fetch the first row and immediately close
  37. all remaining cursor/connection resources.
  38. RETURNING support
  39. -----------------
  40. Firebird 2.0 supports returning a result set from inserts, and 2.1
  41. extends that to deletes and updates. This is generically exposed by
  42. the SQLAlchemy ``returning()`` method, such as::
  43. # INSERT..RETURNING
  44. result = table.insert().returning(table.c.col1, table.c.col2).\
  45. values(name='foo')
  46. print result.fetchall()
  47. # UPDATE..RETURNING
  48. raises = empl.update().returning(empl.c.id, empl.c.salary).\
  49. where(empl.c.sales>100).\
  50. values(dict(salary=empl.c.salary * 1.1))
  51. print raises.fetchall()
  52. .. _dialects: http://mc-computing.com/Databases/Firebird/SQL_Dialect.html
  53. """
  54. import datetime
  55. from sqlalchemy import schema as sa_schema
  56. from sqlalchemy import exc, types as sqltypes, sql, util
  57. from sqlalchemy.sql import expression
  58. from sqlalchemy.engine import base, default, reflection
  59. from sqlalchemy.sql import compiler
  60. from sqlalchemy.sql.elements import quoted_name
  61. from sqlalchemy.types import (BIGINT, BLOB, DATE, FLOAT, INTEGER, NUMERIC,
  62. SMALLINT, TEXT, TIME, TIMESTAMP, Integer)
  63. RESERVED_WORDS = set([
  64. "active", "add", "admin", "after", "all", "alter", "and", "any", "as",
  65. "asc", "ascending", "at", "auto", "avg", "before", "begin", "between",
  66. "bigint", "bit_length", "blob", "both", "by", "case", "cast", "char",
  67. "character", "character_length", "char_length", "check", "close",
  68. "collate", "column", "commit", "committed", "computed", "conditional",
  69. "connect", "constraint", "containing", "count", "create", "cross",
  70. "cstring", "current", "current_connection", "current_date",
  71. "current_role", "current_time", "current_timestamp",
  72. "current_transaction", "current_user", "cursor", "database", "date",
  73. "day", "dec", "decimal", "declare", "default", "delete", "desc",
  74. "descending", "disconnect", "distinct", "do", "domain", "double",
  75. "drop", "else", "end", "entry_point", "escape", "exception",
  76. "execute", "exists", "exit", "external", "extract", "fetch", "file",
  77. "filter", "float", "for", "foreign", "from", "full", "function",
  78. "gdscode", "generator", "gen_id", "global", "grant", "group",
  79. "having", "hour", "if", "in", "inactive", "index", "inner",
  80. "input_type", "insensitive", "insert", "int", "integer", "into", "is",
  81. "isolation", "join", "key", "leading", "left", "length", "level",
  82. "like", "long", "lower", "manual", "max", "maximum_segment", "merge",
  83. "min", "minute", "module_name", "month", "names", "national",
  84. "natural", "nchar", "no", "not", "null", "numeric", "octet_length",
  85. "of", "on", "only", "open", "option", "or", "order", "outer",
  86. "output_type", "overflow", "page", "pages", "page_size", "parameter",
  87. "password", "plan", "position", "post_event", "precision", "primary",
  88. "privileges", "procedure", "protected", "rdb$db_key", "read", "real",
  89. "record_version", "recreate", "recursive", "references", "release",
  90. "reserv", "reserving", "retain", "returning_values", "returns",
  91. "revoke", "right", "rollback", "rows", "row_count", "savepoint",
  92. "schema", "second", "segment", "select", "sensitive", "set", "shadow",
  93. "shared", "singular", "size", "smallint", "snapshot", "some", "sort",
  94. "sqlcode", "stability", "start", "starting", "starts", "statistics",
  95. "sub_type", "sum", "suspend", "table", "then", "time", "timestamp",
  96. "to", "trailing", "transaction", "trigger", "trim", "uncommitted",
  97. "union", "unique", "update", "upper", "user", "using", "value",
  98. "values", "varchar", "variable", "varying", "view", "wait", "when",
  99. "where", "while", "with", "work", "write", "year",
  100. ])
  101. class _StringType(sqltypes.String):
  102. """Base for Firebird string types."""
  103. def __init__(self, charset=None, **kw):
  104. self.charset = charset
  105. super(_StringType, self).__init__(**kw)
  106. class VARCHAR(_StringType, sqltypes.VARCHAR):
  107. """Firebird VARCHAR type"""
  108. __visit_name__ = 'VARCHAR'
  109. def __init__(self, length=None, **kwargs):
  110. super(VARCHAR, self).__init__(length=length, **kwargs)
  111. class CHAR(_StringType, sqltypes.CHAR):
  112. """Firebird CHAR type"""
  113. __visit_name__ = 'CHAR'
  114. def __init__(self, length=None, **kwargs):
  115. super(CHAR, self).__init__(length=length, **kwargs)
  116. class _FBDateTime(sqltypes.DateTime):
  117. def bind_processor(self, dialect):
  118. def process(value):
  119. if type(value) == datetime.date:
  120. return datetime.datetime(value.year, value.month, value.day)
  121. else:
  122. return value
  123. return process
  124. colspecs = {
  125. sqltypes.DateTime: _FBDateTime
  126. }
  127. ischema_names = {
  128. 'SHORT': SMALLINT,
  129. 'LONG': INTEGER,
  130. 'QUAD': FLOAT,
  131. 'FLOAT': FLOAT,
  132. 'DATE': DATE,
  133. 'TIME': TIME,
  134. 'TEXT': TEXT,
  135. 'INT64': BIGINT,
  136. 'DOUBLE': FLOAT,
  137. 'TIMESTAMP': TIMESTAMP,
  138. 'VARYING': VARCHAR,
  139. 'CSTRING': CHAR,
  140. 'BLOB': BLOB,
  141. }
  142. # TODO: date conversion types (should be implemented as _FBDateTime,
  143. # _FBDate, etc. as bind/result functionality is required)
  144. class FBTypeCompiler(compiler.GenericTypeCompiler):
  145. def visit_boolean(self, type_, **kw):
  146. return self.visit_SMALLINT(type_, **kw)
  147. def visit_datetime(self, type_, **kw):
  148. return self.visit_TIMESTAMP(type_, **kw)
  149. def visit_TEXT(self, type_, **kw):
  150. return "BLOB SUB_TYPE 1"
  151. def visit_BLOB(self, type_, **kw):
  152. return "BLOB SUB_TYPE 0"
  153. def _extend_string(self, type_, basic):
  154. charset = getattr(type_, 'charset', None)
  155. if charset is None:
  156. return basic
  157. else:
  158. return '%s CHARACTER SET %s' % (basic, charset)
  159. def visit_CHAR(self, type_, **kw):
  160. basic = super(FBTypeCompiler, self).visit_CHAR(type_, **kw)
  161. return self._extend_string(type_, basic)
  162. def visit_VARCHAR(self, type_, **kw):
  163. if not type_.length:
  164. raise exc.CompileError(
  165. "VARCHAR requires a length on dialect %s" %
  166. self.dialect.name)
  167. basic = super(FBTypeCompiler, self).visit_VARCHAR(type_, **kw)
  168. return self._extend_string(type_, basic)
  169. class FBCompiler(sql.compiler.SQLCompiler):
  170. """Firebird specific idiosyncrasies"""
  171. ansi_bind_rules = True
  172. # def visit_contains_op_binary(self, binary, operator, **kw):
  173. # cant use CONTAINING b.c. it's case insensitive.
  174. # def visit_notcontains_op_binary(self, binary, operator, **kw):
  175. # cant use NOT CONTAINING b.c. it's case insensitive.
  176. def visit_now_func(self, fn, **kw):
  177. return "CURRENT_TIMESTAMP"
  178. def visit_startswith_op_binary(self, binary, operator, **kw):
  179. return '%s STARTING WITH %s' % (
  180. binary.left._compiler_dispatch(self, **kw),
  181. binary.right._compiler_dispatch(self, **kw))
  182. def visit_notstartswith_op_binary(self, binary, operator, **kw):
  183. return '%s NOT STARTING WITH %s' % (
  184. binary.left._compiler_dispatch(self, **kw),
  185. binary.right._compiler_dispatch(self, **kw))
  186. def visit_mod_binary(self, binary, operator, **kw):
  187. return "mod(%s, %s)" % (
  188. self.process(binary.left, **kw),
  189. self.process(binary.right, **kw))
  190. def visit_alias(self, alias, asfrom=False, **kwargs):
  191. if self.dialect._version_two:
  192. return super(FBCompiler, self).\
  193. visit_alias(alias, asfrom=asfrom, **kwargs)
  194. else:
  195. # Override to not use the AS keyword which FB 1.5 does not like
  196. if asfrom:
  197. alias_name = isinstance(alias.name,
  198. expression._truncated_label) and \
  199. self._truncated_identifier("alias",
  200. alias.name) or alias.name
  201. return self.process(
  202. alias.original, asfrom=asfrom, **kwargs) + \
  203. " " + \
  204. self.preparer.format_alias(alias, alias_name)
  205. else:
  206. return self.process(alias.original, **kwargs)
  207. def visit_substring_func(self, func, **kw):
  208. s = self.process(func.clauses.clauses[0])
  209. start = self.process(func.clauses.clauses[1])
  210. if len(func.clauses.clauses) > 2:
  211. length = self.process(func.clauses.clauses[2])
  212. return "SUBSTRING(%s FROM %s FOR %s)" % (s, start, length)
  213. else:
  214. return "SUBSTRING(%s FROM %s)" % (s, start)
  215. def visit_length_func(self, function, **kw):
  216. if self.dialect._version_two:
  217. return "char_length" + self.function_argspec(function)
  218. else:
  219. return "strlen" + self.function_argspec(function)
  220. visit_char_length_func = visit_length_func
  221. def function_argspec(self, func, **kw):
  222. # TODO: this probably will need to be
  223. # narrowed to a fixed list, some no-arg functions
  224. # may require parens - see similar example in the oracle
  225. # dialect
  226. if func.clauses is not None and len(func.clauses):
  227. return self.process(func.clause_expr, **kw)
  228. else:
  229. return ""
  230. def default_from(self):
  231. return " FROM rdb$database"
  232. def visit_sequence(self, seq):
  233. return "gen_id(%s, 1)" % self.preparer.format_sequence(seq)
  234. def get_select_precolumns(self, select, **kw):
  235. """Called when building a ``SELECT`` statement, position is just
  236. before column list Firebird puts the limit and offset right
  237. after the ``SELECT``...
  238. """
  239. result = ""
  240. if select._limit_clause is not None:
  241. result += "FIRST %s " % self.process(select._limit_clause, **kw)
  242. if select._offset_clause is not None:
  243. result += "SKIP %s " % self.process(select._offset_clause, **kw)
  244. if select._distinct:
  245. result += "DISTINCT "
  246. return result
  247. def limit_clause(self, select, **kw):
  248. """Already taken care of in the `get_select_precolumns` method."""
  249. return ""
  250. def returning_clause(self, stmt, returning_cols):
  251. columns = [
  252. self._label_select_column(None, c, True, False, {})
  253. for c in expression._select_iterables(returning_cols)
  254. ]
  255. return 'RETURNING ' + ', '.join(columns)
  256. class FBDDLCompiler(sql.compiler.DDLCompiler):
  257. """Firebird syntactic idiosyncrasies"""
  258. def visit_create_sequence(self, create):
  259. """Generate a ``CREATE GENERATOR`` statement for the sequence."""
  260. # no syntax for these
  261. # http://www.firebirdsql.org/manual/generatorguide-sqlsyntax.html
  262. if create.element.start is not None:
  263. raise NotImplemented(
  264. "Firebird SEQUENCE doesn't support START WITH")
  265. if create.element.increment is not None:
  266. raise NotImplemented(
  267. "Firebird SEQUENCE doesn't support INCREMENT BY")
  268. if self.dialect._version_two:
  269. return "CREATE SEQUENCE %s" % \
  270. self.preparer.format_sequence(create.element)
  271. else:
  272. return "CREATE GENERATOR %s" % \
  273. self.preparer.format_sequence(create.element)
  274. def visit_drop_sequence(self, drop):
  275. """Generate a ``DROP GENERATOR`` statement for the sequence."""
  276. if self.dialect._version_two:
  277. return "DROP SEQUENCE %s" % \
  278. self.preparer.format_sequence(drop.element)
  279. else:
  280. return "DROP GENERATOR %s" % \
  281. self.preparer.format_sequence(drop.element)
  282. class FBIdentifierPreparer(sql.compiler.IdentifierPreparer):
  283. """Install Firebird specific reserved words."""
  284. reserved_words = RESERVED_WORDS
  285. illegal_initial_characters = compiler.ILLEGAL_INITIAL_CHARACTERS.union(
  286. ['_'])
  287. def __init__(self, dialect):
  288. super(FBIdentifierPreparer, self).__init__(dialect, omit_schema=True)
  289. class FBExecutionContext(default.DefaultExecutionContext):
  290. def fire_sequence(self, seq, type_):
  291. """Get the next value from the sequence using ``gen_id()``."""
  292. return self._execute_scalar(
  293. "SELECT gen_id(%s, 1) FROM rdb$database" %
  294. self.dialect.identifier_preparer.format_sequence(seq),
  295. type_
  296. )
  297. class FBDialect(default.DefaultDialect):
  298. """Firebird dialect"""
  299. name = 'firebird'
  300. max_identifier_length = 31
  301. supports_sequences = True
  302. sequences_optional = False
  303. supports_default_values = True
  304. postfetch_lastrowid = False
  305. supports_native_boolean = False
  306. requires_name_normalize = True
  307. supports_empty_insert = False
  308. statement_compiler = FBCompiler
  309. ddl_compiler = FBDDLCompiler
  310. preparer = FBIdentifierPreparer
  311. type_compiler = FBTypeCompiler
  312. execution_ctx_cls = FBExecutionContext
  313. colspecs = colspecs
  314. ischema_names = ischema_names
  315. construct_arguments = []
  316. # defaults to dialect ver. 3,
  317. # will be autodetected off upon
  318. # first connect
  319. _version_two = True
  320. def initialize(self, connection):
  321. super(FBDialect, self).initialize(connection)
  322. self._version_two = ('firebird' in self.server_version_info and
  323. self.server_version_info >= (2, )
  324. ) or \
  325. ('interbase' in self.server_version_info and
  326. self.server_version_info >= (6, )
  327. )
  328. if not self._version_two:
  329. # TODO: whatever other pre < 2.0 stuff goes here
  330. self.ischema_names = ischema_names.copy()
  331. self.ischema_names['TIMESTAMP'] = sqltypes.DATE
  332. self.colspecs = {
  333. sqltypes.DateTime: sqltypes.DATE
  334. }
  335. self.implicit_returning = self._version_two and \
  336. self.__dict__.get('implicit_returning', True)
  337. def normalize_name(self, name):
  338. # Remove trailing spaces: FB uses a CHAR() type,
  339. # that is padded with spaces
  340. name = name and name.rstrip()
  341. if name is None:
  342. return None
  343. elif name.upper() == name and \
  344. not self.identifier_preparer._requires_quotes(name.lower()):
  345. return name.lower()
  346. elif name.lower() == name:
  347. return quoted_name(name, quote=True)
  348. else:
  349. return name
  350. def denormalize_name(self, name):
  351. if name is None:
  352. return None
  353. elif name.lower() == name and \
  354. not self.identifier_preparer._requires_quotes(name.lower()):
  355. return name.upper()
  356. else:
  357. return name
  358. def has_table(self, connection, table_name, schema=None):
  359. """Return ``True`` if the given table exists, ignoring
  360. the `schema`."""
  361. tblqry = """
  362. SELECT 1 AS has_table FROM rdb$database
  363. WHERE EXISTS (SELECT rdb$relation_name
  364. FROM rdb$relations
  365. WHERE rdb$relation_name=?)
  366. """
  367. c = connection.execute(tblqry, [self.denormalize_name(table_name)])
  368. return c.first() is not None
  369. def has_sequence(self, connection, sequence_name, schema=None):
  370. """Return ``True`` if the given sequence (generator) exists."""
  371. genqry = """
  372. SELECT 1 AS has_sequence FROM rdb$database
  373. WHERE EXISTS (SELECT rdb$generator_name
  374. FROM rdb$generators
  375. WHERE rdb$generator_name=?)
  376. """
  377. c = connection.execute(genqry, [self.denormalize_name(sequence_name)])
  378. return c.first() is not None
  379. @reflection.cache
  380. def get_table_names(self, connection, schema=None, **kw):
  381. # there are two queries commonly mentioned for this.
  382. # this one, using view_blr, is at the Firebird FAQ among other places:
  383. # http://www.firebirdfaq.org/faq174/
  384. s = """
  385. select rdb$relation_name
  386. from rdb$relations
  387. where rdb$view_blr is null
  388. and (rdb$system_flag is null or rdb$system_flag = 0);
  389. """
  390. # the other query is this one. It's not clear if there's really
  391. # any difference between these two. This link:
  392. # http://www.alberton.info/firebird_sql_meta_info.html#.Ur3vXfZGni8
  393. # states them as interchangeable. Some discussion at [ticket:2898]
  394. # SELECT DISTINCT rdb$relation_name
  395. # FROM rdb$relation_fields
  396. # WHERE rdb$system_flag=0 AND rdb$view_context IS NULL
  397. return [self.normalize_name(row[0]) for row in connection.execute(s)]
  398. @reflection.cache
  399. def get_view_names(self, connection, schema=None, **kw):
  400. # see http://www.firebirdfaq.org/faq174/
  401. s = """
  402. select rdb$relation_name
  403. from rdb$relations
  404. where rdb$view_blr is not null
  405. and (rdb$system_flag is null or rdb$system_flag = 0);
  406. """
  407. return [self.normalize_name(row[0]) for row in connection.execute(s)]
  408. @reflection.cache
  409. def get_view_definition(self, connection, view_name, schema=None, **kw):
  410. qry = """
  411. SELECT rdb$view_source AS view_source
  412. FROM rdb$relations
  413. WHERE rdb$relation_name=?
  414. """
  415. rp = connection.execute(qry, [self.denormalize_name(view_name)])
  416. row = rp.first()
  417. if row:
  418. return row['view_source']
  419. else:
  420. return None
  421. @reflection.cache
  422. def get_pk_constraint(self, connection, table_name, schema=None, **kw):
  423. # Query to extract the PK/FK constrained fields of the given table
  424. keyqry = """
  425. SELECT se.rdb$field_name AS fname
  426. FROM rdb$relation_constraints rc
  427. JOIN rdb$index_segments se ON rc.rdb$index_name=se.rdb$index_name
  428. WHERE rc.rdb$constraint_type=? AND rc.rdb$relation_name=?
  429. """
  430. tablename = self.denormalize_name(table_name)
  431. # get primary key fields
  432. c = connection.execute(keyqry, ["PRIMARY KEY", tablename])
  433. pkfields = [self.normalize_name(r['fname']) for r in c.fetchall()]
  434. return {'constrained_columns': pkfields, 'name': None}
  435. @reflection.cache
  436. def get_column_sequence(self, connection,
  437. table_name, column_name,
  438. schema=None, **kw):
  439. tablename = self.denormalize_name(table_name)
  440. colname = self.denormalize_name(column_name)
  441. # Heuristic-query to determine the generator associated to a PK field
  442. genqry = """
  443. SELECT trigdep.rdb$depended_on_name AS fgenerator
  444. FROM rdb$dependencies tabdep
  445. JOIN rdb$dependencies trigdep
  446. ON tabdep.rdb$dependent_name=trigdep.rdb$dependent_name
  447. AND trigdep.rdb$depended_on_type=14
  448. AND trigdep.rdb$dependent_type=2
  449. JOIN rdb$triggers trig ON
  450. trig.rdb$trigger_name=tabdep.rdb$dependent_name
  451. WHERE tabdep.rdb$depended_on_name=?
  452. AND tabdep.rdb$depended_on_type=0
  453. AND trig.rdb$trigger_type=1
  454. AND tabdep.rdb$field_name=?
  455. AND (SELECT count(*)
  456. FROM rdb$dependencies trigdep2
  457. WHERE trigdep2.rdb$dependent_name = trigdep.rdb$dependent_name) = 2
  458. """
  459. genr = connection.execute(genqry, [tablename, colname]).first()
  460. if genr is not None:
  461. return dict(name=self.normalize_name(genr['fgenerator']))
  462. @reflection.cache
  463. def get_columns(self, connection, table_name, schema=None, **kw):
  464. # Query to extract the details of all the fields of the given table
  465. tblqry = """
  466. SELECT r.rdb$field_name AS fname,
  467. r.rdb$null_flag AS null_flag,
  468. t.rdb$type_name AS ftype,
  469. f.rdb$field_sub_type AS stype,
  470. f.rdb$field_length/
  471. COALESCE(cs.rdb$bytes_per_character,1) AS flen,
  472. f.rdb$field_precision AS fprec,
  473. f.rdb$field_scale AS fscale,
  474. COALESCE(r.rdb$default_source,
  475. f.rdb$default_source) AS fdefault
  476. FROM rdb$relation_fields r
  477. JOIN rdb$fields f ON r.rdb$field_source=f.rdb$field_name
  478. JOIN rdb$types t
  479. ON t.rdb$type=f.rdb$field_type AND
  480. t.rdb$field_name='RDB$FIELD_TYPE'
  481. LEFT JOIN rdb$character_sets cs ON
  482. f.rdb$character_set_id=cs.rdb$character_set_id
  483. WHERE f.rdb$system_flag=0 AND r.rdb$relation_name=?
  484. ORDER BY r.rdb$field_position
  485. """
  486. # get the PK, used to determine the eventual associated sequence
  487. pk_constraint = self.get_pk_constraint(connection, table_name)
  488. pkey_cols = pk_constraint['constrained_columns']
  489. tablename = self.denormalize_name(table_name)
  490. # get all of the fields for this table
  491. c = connection.execute(tblqry, [tablename])
  492. cols = []
  493. while True:
  494. row = c.fetchone()
  495. if row is None:
  496. break
  497. name = self.normalize_name(row['fname'])
  498. orig_colname = row['fname']
  499. # get the data type
  500. colspec = row['ftype'].rstrip()
  501. coltype = self.ischema_names.get(colspec)
  502. if coltype is None:
  503. util.warn("Did not recognize type '%s' of column '%s'" %
  504. (colspec, name))
  505. coltype = sqltypes.NULLTYPE
  506. elif issubclass(coltype, Integer) and row['fprec'] != 0:
  507. coltype = NUMERIC(
  508. precision=row['fprec'],
  509. scale=row['fscale'] * -1)
  510. elif colspec in ('VARYING', 'CSTRING'):
  511. coltype = coltype(row['flen'])
  512. elif colspec == 'TEXT':
  513. coltype = TEXT(row['flen'])
  514. elif colspec == 'BLOB':
  515. if row['stype'] == 1:
  516. coltype = TEXT()
  517. else:
  518. coltype = BLOB()
  519. else:
  520. coltype = coltype()
  521. # does it have a default value?
  522. defvalue = None
  523. if row['fdefault'] is not None:
  524. # the value comes down as "DEFAULT 'value'": there may be
  525. # more than one whitespace around the "DEFAULT" keyword
  526. # and it may also be lower case
  527. # (see also http://tracker.firebirdsql.org/browse/CORE-356)
  528. defexpr = row['fdefault'].lstrip()
  529. assert defexpr[:8].rstrip().upper() == \
  530. 'DEFAULT', "Unrecognized default value: %s" % \
  531. defexpr
  532. defvalue = defexpr[8:].strip()
  533. if defvalue == 'NULL':
  534. # Redundant
  535. defvalue = None
  536. col_d = {
  537. 'name': name,
  538. 'type': coltype,
  539. 'nullable': not bool(row['null_flag']),
  540. 'default': defvalue,
  541. 'autoincrement': 'auto',
  542. }
  543. if orig_colname.lower() == orig_colname:
  544. col_d['quote'] = True
  545. # if the PK is a single field, try to see if its linked to
  546. # a sequence thru a trigger
  547. if len(pkey_cols) == 1 and name == pkey_cols[0]:
  548. seq_d = self.get_column_sequence(connection, tablename, name)
  549. if seq_d is not None:
  550. col_d['sequence'] = seq_d
  551. cols.append(col_d)
  552. return cols
  553. @reflection.cache
  554. def get_foreign_keys(self, connection, table_name, schema=None, **kw):
  555. # Query to extract the details of each UK/FK of the given table
  556. fkqry = """
  557. SELECT rc.rdb$constraint_name AS cname,
  558. cse.rdb$field_name AS fname,
  559. ix2.rdb$relation_name AS targetrname,
  560. se.rdb$field_name AS targetfname
  561. FROM rdb$relation_constraints rc
  562. JOIN rdb$indices ix1 ON ix1.rdb$index_name=rc.rdb$index_name
  563. JOIN rdb$indices ix2 ON ix2.rdb$index_name=ix1.rdb$foreign_key
  564. JOIN rdb$index_segments cse ON
  565. cse.rdb$index_name=ix1.rdb$index_name
  566. JOIN rdb$index_segments se
  567. ON se.rdb$index_name=ix2.rdb$index_name
  568. AND se.rdb$field_position=cse.rdb$field_position
  569. WHERE rc.rdb$constraint_type=? AND rc.rdb$relation_name=?
  570. ORDER BY se.rdb$index_name, se.rdb$field_position
  571. """
  572. tablename = self.denormalize_name(table_name)
  573. c = connection.execute(fkqry, ["FOREIGN KEY", tablename])
  574. fks = util.defaultdict(lambda: {
  575. 'name': None,
  576. 'constrained_columns': [],
  577. 'referred_schema': None,
  578. 'referred_table': None,
  579. 'referred_columns': []
  580. })
  581. for row in c:
  582. cname = self.normalize_name(row['cname'])
  583. fk = fks[cname]
  584. if not fk['name']:
  585. fk['name'] = cname
  586. fk['referred_table'] = self.normalize_name(row['targetrname'])
  587. fk['constrained_columns'].append(
  588. self.normalize_name(row['fname']))
  589. fk['referred_columns'].append(
  590. self.normalize_name(row['targetfname']))
  591. return list(fks.values())
  592. @reflection.cache
  593. def get_indexes(self, connection, table_name, schema=None, **kw):
  594. qry = """
  595. SELECT ix.rdb$index_name AS index_name,
  596. ix.rdb$unique_flag AS unique_flag,
  597. ic.rdb$field_name AS field_name
  598. FROM rdb$indices ix
  599. JOIN rdb$index_segments ic
  600. ON ix.rdb$index_name=ic.rdb$index_name
  601. LEFT OUTER JOIN rdb$relation_constraints
  602. ON rdb$relation_constraints.rdb$index_name =
  603. ic.rdb$index_name
  604. WHERE ix.rdb$relation_name=? AND ix.rdb$foreign_key IS NULL
  605. AND rdb$relation_constraints.rdb$constraint_type IS NULL
  606. ORDER BY index_name, ic.rdb$field_position
  607. """
  608. c = connection.execute(qry, [self.denormalize_name(table_name)])
  609. indexes = util.defaultdict(dict)
  610. for row in c:
  611. indexrec = indexes[row['index_name']]
  612. if 'name' not in indexrec:
  613. indexrec['name'] = self.normalize_name(row['index_name'])
  614. indexrec['column_names'] = []
  615. indexrec['unique'] = bool(row['unique_flag'])
  616. indexrec['column_names'].append(
  617. self.normalize_name(row['field_name']))
  618. return list(indexes.values())