db_setup.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. from sqlite3 import OperationalError
  2. from game import CURRENCY_NAME, MINIMUM_ORDER_AMOUNT
  3. def setup(cursor):
  4. print('Database setup...')
  5. drop_triggers(cursor)
  6. tables(cursor)
  7. create_triggers(cursor)
  8. create_indices(cursor)
  9. seed(cursor)
  10. def drop_triggers(cursor):
  11. print(' - Dropping all triggers...')
  12. cursor.execute("DROP TRIGGER IF EXISTS owned_amount_not_negative_after_insert")
  13. cursor.execute("DROP TRIGGER IF EXISTS owned_amount_not_negative_after_update")
  14. cursor.execute("DROP TRIGGER IF EXISTS amount_positive_after_insert")
  15. cursor.execute("DROP TRIGGER IF EXISTS amount_positive_after_update")
  16. cursor.execute("DROP TRIGGER IF EXISTS order_limit_not_negative_after_insert")
  17. cursor.execute("DROP TRIGGER IF EXISTS order_limit_not_negative_after_update")
  18. cursor.execute("DROP TRIGGER IF EXISTS order_amount_positive_after_insert")
  19. cursor.execute("DROP TRIGGER IF EXISTS order_amount_positive_after_update")
  20. cursor.execute("DROP TRIGGER IF EXISTS not_more_executed_than_ordered_after_insert")
  21. cursor.execute("DROP TRIGGER IF EXISTS not_more_executed_than_ordered_after_update")
  22. cursor.execute("DROP TRIGGER IF EXISTS not_more_ordered_than_available_after_insert")
  23. cursor.execute("DROP TRIGGER IF EXISTS not_more_ordered_than_available_after_update")
  24. cursor.execute("DROP TRIGGER IF EXISTS expiry_dt_in_future_after_insert")
  25. cursor.execute("DROP TRIGGER IF EXISTS expiry_dt_in_future_after_update")
  26. cursor.execute("DROP TRIGGER IF EXISTS stop_loss_requires_limit_after_insert")
  27. cursor.execute("DROP TRIGGER IF EXISTS stop_loss_requires_limit_after_update")
  28. cursor.execute("DROP TRIGGER IF EXISTS limit_requires_stop_loss_after_insert")
  29. cursor.execute("DROP TRIGGER IF EXISTS limit_requires_stop_loss_after_update")
  30. cursor.execute("DROP TRIGGER IF EXISTS minimum_order_amount_after_insert")
  31. cursor.execute("DROP TRIGGER IF EXISTS integer_amount_after_insert")
  32. cursor.execute("DROP TRIGGER IF EXISTS dt_monotonic_after_insert")
  33. cursor.execute("DROP TRIGGER IF EXISTS dt_monotonic_after_update")
  34. cursor.execute("DROP TRIGGER IF EXISTS orders_rowid_sorted_by_creation_time_after_insert")
  35. cursor.execute("DROP TRIGGER IF EXISTS news_dt_monotonic_after_update")
  36. cursor.execute("DROP TRIGGER IF EXISTS not_nullify_buyer_id_after_update")
  37. cursor.execute("DROP TRIGGER IF EXISTS buyer_id_not_null_after_insert")
  38. cursor.execute("DROP TRIGGER IF EXISTS not_nullify_seller_id_after_update")
  39. cursor.execute("DROP TRIGGER IF EXISTS seller_id_not_null_after_insert")
  40. cursor.execute("DROP TRIGGER IF EXISTS order_history_no_update")
  41. def seed(cursor):
  42. print(' - Seeding initial data...')
  43. # ₭ollar
  44. cursor.execute('''
  45. INSERT OR IGNORE INTO ownables
  46. (name)
  47. VALUES (?)
  48. ''', (CURRENCY_NAME,))
  49. # The bank/external investors
  50. cursor.execute('''
  51. INSERT OR IGNORE INTO users
  52. (username,password)
  53. VALUES ('bank','')
  54. ''')
  55. # bank owns all the money that is not owned by players, 1000 * num_used_key - player_money
  56. cursor.execute('''
  57. INSERT OR IGNORE INTO ownership
  58. (user_id, ownable_id, amount)
  59. VALUES ((SELECT rowid FROM users WHERE username = 'bank'),
  60. (SELECT rowid FROM ownables WHERE name = ?),
  61. 1000 * (SELECT COUNT(used_by_user_id) FROM keys) - (SELECT SUM(amount)
  62. FROM ownership
  63. WHERE ownable_id = (SELECT rowid FROM ownables WHERE name = ?)))
  64. ''', (CURRENCY_NAME, CURRENCY_NAME,))
  65. # bank owns some stuff (₭ollar is be dealt with separately)
  66. cursor.execute('''
  67. INSERT OR IGNORE INTO ownership
  68. (user_id, ownable_id, amount)
  69. SELECT (SELECT rowid FROM users WHERE username = 'bank'),
  70. ownables.rowid,
  71. (SELECT COALESCE(SUM(amount),0) FROM ownership WHERE ownable_id = ownables.rowid)
  72. FROM ownables WHERE
  73. name <> ?
  74. ''', (CURRENCY_NAME,))
  75. cursor.executemany('''
  76. INSERT INTO global_control_values (value_name, value)
  77. WITH new_value AS (SELECT ? AS name, ? AS value)
  78. SELECT new_value.name, new_value.value
  79. FROM new_value
  80. WHERE NOT EXISTS(SELECT * -- tODO test if this works
  81. FROM global_control_values v2
  82. WHERE v2.value_name = new_value.name
  83. AND v2.value = new_value.value
  84. AND v2.dt = (SELECT MAX(v3.dt)
  85. FROM global_control_values v3
  86. WHERE v3.value_name = new_value.name
  87. AND v3.value = new_value.value))
  88. ''', [('banking_license_price', 5e6),
  89. ('personal_loan_interest_rate', 0.1), # may seem a lot but actually this is a credit that you get without any assessment involved
  90. ('deposit_facility', -0.05), # ECB 2020
  91. ('marginal_lending_facility', 0.25), # ECB 2020
  92. ('cash_reserve_ratio', 0.01), # Eurozone 2020
  93. ('cash_reserve_free_amount', 1e5), # Eurozone 2020
  94. ])
  95. def create_triggers(cursor):
  96. print(' - Creating triggers...')
  97. cursor.execute('''
  98. CREATE TRIGGER IF NOT EXISTS owned_amount_not_negative_after_insert
  99. AFTER INSERT ON ownership
  100. WHEN NEW.amount < 0
  101. BEGIN SELECT RAISE(ROLLBACK, 'Can not own an amount less than 0.'); END
  102. ''')
  103. cursor.execute('''
  104. CREATE TRIGGER IF NOT EXISTS owned_amount_not_negative_after_update
  105. AFTER UPDATE ON ownership
  106. WHEN NEW.amount < 0
  107. BEGIN SELECT RAISE(ROLLBACK, 'Can not own an amount less than 0.'); END
  108. ''')
  109. cursor.execute('''
  110. CREATE TRIGGER IF NOT EXISTS amount_positive_after_insert
  111. AFTER INSERT ON transactions
  112. WHEN NEW.amount <= 0
  113. BEGIN SELECT RAISE(ROLLBACK, 'Can not perform empty transactions.'); END
  114. ''')
  115. cursor.execute('''
  116. CREATE TRIGGER IF NOT EXISTS amount_positive_after_update
  117. AFTER UPDATE ON transactions
  118. WHEN NEW.amount <= 0
  119. BEGIN SELECT RAISE(ROLLBACK, 'Can not perform empty transactions.'); END
  120. ''')
  121. cursor.execute('''
  122. CREATE TRIGGER IF NOT EXISTS order_limit_not_negative_after_insert
  123. AFTER INSERT ON orders
  124. WHEN NEW."limit" IS NOT NULL AND NEW."limit" < 0
  125. BEGIN SELECT RAISE(ROLLBACK, 'Can not set a limit less than 0.'); END
  126. ''')
  127. cursor.execute('''
  128. CREATE TRIGGER IF NOT EXISTS order_limit_not_negative_after_update
  129. AFTER UPDATE ON orders
  130. WHEN NEW."limit" IS NOT NULL AND NEW."limit" < 0
  131. BEGIN SELECT RAISE(ROLLBACK, 'Can not set a limit less than 0.'); END
  132. ''')
  133. cursor.execute('''
  134. CREATE TRIGGER IF NOT EXISTS order_amount_positive_after_insert
  135. AFTER INSERT ON orders
  136. WHEN NEW.ordered_amount <= 0 OR NEW.executed_amount < 0
  137. BEGIN SELECT RAISE(ROLLBACK, 'Can not order 0 or less.'); END
  138. ''')
  139. cursor.execute('''
  140. CREATE TRIGGER IF NOT EXISTS order_amount_positive_after_update
  141. AFTER UPDATE ON orders
  142. WHEN NEW.ordered_amount <= 0 OR NEW.executed_amount < 0
  143. BEGIN SELECT RAISE(ROLLBACK, 'Can not order 0 or less.'); END
  144. ''')
  145. cursor.execute('''
  146. CREATE TRIGGER IF NOT EXISTS not_more_executed_than_ordered_after_insert
  147. AFTER INSERT ON orders
  148. WHEN NEW.ordered_amount < NEW.executed_amount
  149. BEGIN SELECT RAISE(ROLLBACK, 'Can not execute more than ordered.'); END
  150. ''')
  151. cursor.execute('''
  152. CREATE TRIGGER IF NOT EXISTS not_more_executed_than_ordered_after_update
  153. AFTER UPDATE ON orders
  154. WHEN NEW.ordered_amount < NEW.executed_amount
  155. BEGIN SELECT RAISE(ROLLBACK, 'Can not execute more than ordered.'); END
  156. ''')
  157. cursor.execute('''
  158. CREATE TRIGGER IF NOT EXISTS not_more_ordered_than_available_after_insert
  159. AFTER INSERT ON orders
  160. WHEN NOT NEW.buy AND 0 >
  161. -- owned_amount
  162. COALESCE (
  163. (SELECT amount
  164. FROM ownership
  165. WHERE ownership.rowid = NEW.ownership_id), 0)
  166. - -- sell_ordered_amount
  167. (SELECT COALESCE(SUM(orders.ordered_amount - orders.executed_amount),0)
  168. FROM orders, ownership
  169. WHERE ownership.rowid = orders.ownership_id
  170. AND ownership.rowid = NEW.ownership_id
  171. AND NOT orders.buy)
  172. BEGIN SELECT RAISE(ROLLBACK, 'Can not order more than you own.'); END
  173. ''')
  174. cursor.execute('''
  175. CREATE TRIGGER IF NOT EXISTS not_more_ordered_than_available_after_update
  176. AFTER UPDATE ON orders
  177. WHEN NOT NEW.buy AND 0 >
  178. -- owned_amount
  179. COALESCE (
  180. (SELECT amount
  181. FROM ownership
  182. WHERE ownership.rowid = NEW.ownership_id), 0)
  183. - -- sell_ordered_amount
  184. (SELECT COALESCE(SUM(orders.ordered_amount - orders.executed_amount),0)
  185. FROM orders, ownership
  186. WHERE ownership.rowid = orders.ownership_id
  187. AND ownership.rowid = NEW.ownership_id
  188. AND NOT orders.buy)
  189. BEGIN SELECT RAISE(ROLLBACK, 'Can not order more than you own.'); END
  190. ''')
  191. cursor.execute('''
  192. CREATE TRIGGER IF NOT EXISTS expiry_dt_in_future_after_insert
  193. AFTER INSERT ON orders
  194. WHEN NEW.expiry_dt <= datetime('now')
  195. BEGIN SELECT RAISE(ROLLBACK, 'Order is already expired.'); END
  196. ''')
  197. cursor.execute('''
  198. CREATE TRIGGER IF NOT EXISTS expiry_dt_in_future_after_update
  199. AFTER UPDATE ON orders
  200. WHEN NEW.expiry_dt <= datetime('now')
  201. BEGIN SELECT RAISE(ROLLBACK, 'Order is already expired.'); END
  202. ''')
  203. cursor.execute('''
  204. CREATE TRIGGER IF NOT EXISTS stop_loss_requires_limit_after_insert
  205. AFTER INSERT ON orders
  206. WHEN NEW."limit" IS NULL AND NEW.stop_loss IS NOT NULL
  207. BEGIN SELECT RAISE(ROLLBACK, 'Can only set `stop_loss` `for limit orders.'); END
  208. ''')
  209. cursor.execute('''
  210. CREATE TRIGGER IF NOT EXISTS stop_loss_requires_limit_after_update
  211. AFTER UPDATE ON orders
  212. WHEN NEW."limit" IS NULL AND NEW.stop_loss IS NOT NULL
  213. BEGIN SELECT RAISE(ROLLBACK, 'Can only set `stop_loss` `for limit orders.'); END
  214. ''')
  215. cursor.execute('''
  216. CREATE TRIGGER IF NOT EXISTS limit_requires_stop_loss_after_insert
  217. AFTER INSERT ON orders
  218. WHEN NEW."limit" IS NOT NULL AND NEW.stop_loss IS NULL
  219. BEGIN SELECT RAISE(ROLLBACK, 'Need to set stop_loss to either True or False for limit orders.'); END
  220. ''')
  221. cursor.execute('''
  222. CREATE TRIGGER IF NOT EXISTS limit_requires_stop_loss_after_update
  223. AFTER UPDATE ON orders
  224. WHEN NEW."limit" IS NOT NULL AND NEW.stop_loss IS NULL
  225. BEGIN SELECT RAISE(ROLLBACK, 'Need to set stop_loss to either True or False for limit orders.'); END
  226. ''')
  227. cursor.execute('''
  228. CREATE TRIGGER IF NOT EXISTS minimum_order_amount_after_insert
  229. AFTER INSERT ON orders
  230. WHEN NEW.ordered_amount < ?
  231. BEGIN SELECT RAISE(ROLLBACK, 'There is a minimum amount for new orders.'); END
  232. '''.replace('?', str(MINIMUM_ORDER_AMOUNT)))
  233. cursor.execute('''
  234. CREATE TRIGGER IF NOT EXISTS integer_amount_after_insert
  235. AFTER INSERT ON orders
  236. WHEN NEW.ordered_amount <> ROUND(NEW.ordered_amount)
  237. BEGIN SELECT RAISE(ROLLBACK, 'Can only set integer amounts for new orders.'); END
  238. '''.replace('?', str(MINIMUM_ORDER_AMOUNT)))
  239. cursor.execute('''
  240. CREATE TRIGGER IF NOT EXISTS dt_monotonic_after_insert
  241. AFTER INSERT ON transactions
  242. WHEN NEW.dt < (SELECT MAX(dt) FROM transactions t2 WHERE t2.rowid < rowid)
  243. BEGIN SELECT RAISE(ROLLBACK, 'Transaction rowid programming bug, not your fault.'); END
  244. ''')
  245. cursor.execute('''
  246. CREATE TRIGGER IF NOT EXISTS dt_monotonic_after_update
  247. AFTER INSERT ON transactions
  248. WHEN NEW.dt < (SELECT MAX(dt) FROM transactions t2 WHERE t2.rowid < rowid)
  249. BEGIN SELECT RAISE(ROLLBACK, 'Transaction rowid programming bug, not your fault.'); END
  250. ''')
  251. cursor.execute('''
  252. CREATE TRIGGER IF NOT EXISTS news_dt_monotonic_after_insert
  253. AFTER INSERT ON news
  254. WHEN NEW.dt < (SELECT MAX(dt) FROM news t2 WHERE t2.rowid < rowid)
  255. BEGIN SELECT RAISE(ROLLBACK, 'Transaction rowid programming bug, not your fault.'); END
  256. ''')
  257. cursor.execute('''
  258. CREATE TRIGGER IF NOT EXISTS news_dt_monotonic_after_insert
  259. AFTER INSERT ON news
  260. WHEN NEW.dt < (SELECT MAX(dt) FROM news t2 WHERE t2.rowid < rowid)
  261. BEGIN SELECT RAISE(ROLLBACK, 'Transaction rowid programming bug, not your fault.'); END
  262. ''')
  263. cursor.execute('''
  264. CREATE TRIGGER IF NOT EXISTS orders_rowid_sorted_by_creation_time_after_insert
  265. AFTER INSERT ON orders
  266. WHEN NEW.rowid < (SELECT MAX(rowid) FROM orders o2)
  267. BEGIN SELECT RAISE(ROLLBACK, 'Order-rowid programming bug (insert), not your fault.'); END
  268. ''')
  269. cursor.execute('''
  270. CREATE TRIGGER IF NOT EXISTS news_dt_monotonic_after_update
  271. AFTER UPDATE ON orders
  272. WHEN NEW.rowid <> OLD.rowid
  273. BEGIN SELECT RAISE(ROLLBACK, 'Cannot change number of existing order.'); END
  274. ''')
  275. cursor.execute('''
  276. CREATE TRIGGER IF NOT EXISTS not_nullify_buyer_id_after_update
  277. AFTER UPDATE ON transactions
  278. WHEN NEW.buyer_id IS NULL AND OLD.buyer_id IS NOT NULL
  279. BEGIN SELECT RAISE(ROLLBACK, 'Cannot nullify buyer_id.'); END
  280. ''')
  281. cursor.execute('''
  282. CREATE TRIGGER IF NOT EXISTS buyer_id_not_null_after_insert
  283. AFTER INSERT ON transactions
  284. WHEN NEW.buyer_id IS NULL
  285. BEGIN SELECT RAISE(ROLLBACK, 'buyer_id must not be null for new transactions.'); END
  286. ''')
  287. cursor.execute('''
  288. CREATE TRIGGER IF NOT EXISTS not_nullify_seller_id_after_update
  289. AFTER UPDATE ON transactions
  290. WHEN NEW.seller_id IS NULL AND OLD.seller_id IS NOT NULL
  291. BEGIN SELECT RAISE(ROLLBACK, 'Cannot nullify seller_id.'); END
  292. ''')
  293. cursor.execute('''
  294. CREATE TRIGGER IF NOT EXISTS seller_id_not_null_after_insert
  295. AFTER INSERT ON transactions
  296. WHEN NEW.seller_id IS NULL
  297. BEGIN SELECT RAISE(ROLLBACK, 'seller_id must not be null for new transactions.'); END
  298. ''')
  299. cursor.execute('''
  300. CREATE TRIGGER IF NOT EXISTS order_history_no_update
  301. BEFORE UPDATE ON order_history
  302. BEGIN SELECT RAISE(ROLLBACK, 'Can not change order history.'); END
  303. ''')
  304. def create_indices(cursor):
  305. print(' - Creating indices...')
  306. cursor.execute('''
  307. CREATE INDEX IF NOT EXISTS ownership_ownable
  308. ON ownership (ownable_id)
  309. ''')
  310. cursor.execute('''
  311. CREATE INDEX IF NOT EXISTS transactions_ownable
  312. ON transactions (ownable_id)
  313. ''')
  314. cursor.execute('''
  315. CREATE INDEX IF NOT EXISTS orders_expiry
  316. ON orders (expiry_dt)
  317. ''')
  318. cursor.execute('''
  319. CREATE INDEX IF NOT EXISTS orders_ownership
  320. ON orders (ownership_id)
  321. ''')
  322. cursor.execute('''
  323. CREATE INDEX IF NOT EXISTS orders_limit
  324. ON orders ("limit")
  325. ''')
  326. cursor.execute('''
  327. CREATE INDEX IF NOT EXISTS transactions_dt
  328. ON transactions (dt)
  329. ''')
  330. cursor.execute('''
  331. CREATE INDEX IF NOT EXISTS news_dt
  332. ON news (dt)
  333. ''')
  334. cursor.execute('''
  335. CREATE INDEX IF NOT EXISTS ownables_name
  336. ON ownables (name)
  337. ''')
  338. cursor.execute('''
  339. CREATE INDEX IF NOT EXISTS users_name
  340. ON users (username)
  341. ''')
  342. cursor.execute('''
  343. CREATE INDEX IF NOT EXISTS sessions_id
  344. ON sessions (session_id)
  345. ''')
  346. cursor.execute('''
  347. CREATE INDEX IF NOT EXISTS sessions_user
  348. ON sessions (user_id)
  349. ''')
  350. cursor.execute('''
  351. CREATE INDEX IF NOT EXISTS transactions_seller
  352. ON transactions (seller_id)
  353. ''')
  354. cursor.execute('''
  355. CREATE INDEX IF NOT EXISTS transactions_buyer
  356. ON transactions (buyer_id)
  357. ''')
  358. cursor.execute('''
  359. CREATE INDEX IF NOT EXISTS order_history_id
  360. ON order_history (order_id)
  361. ''')
  362. cursor.execute('''
  363. CREATE INDEX IF NOT EXISTS order_canceled
  364. ON order_history (archived_dt)
  365. ''')
  366. cursor.execute('''
  367. CREATE INDEX IF NOT EXISTS order_history_ownership
  368. ON order_history (ownership_id)
  369. ''')
  370. def tables(cursor):
  371. print(' - Creating tables...')
  372. cursor.execute('''
  373. CREATE TABLE IF NOT EXISTS users(
  374. username VARCHAR(10) UNIQUE NOT NULL,
  375. password VARCHAR(200) NOT NULL)
  376. ''')
  377. cursor.execute('''
  378. CREATE TABLE IF NOT EXISTS ownables(
  379. name VARCHAR(10) UNIQUE NOT NULL)
  380. ''')
  381. cursor.execute('''
  382. CREATE TABLE IF NOT EXISTS ownership(
  383. user_id INTEGER NOT NULL,
  384. ownable_id INTEGER NOT NULL,
  385. amount CURRENCY NOT NULL DEFAULT 0,
  386. FOREIGN KEY (user_id) REFERENCES users(rowid),
  387. FOREIGN KEY (ownable_id) REFERENCES ownables(rowid),
  388. UNIQUE (user_id, ownable_id)
  389. )
  390. ''')
  391. cursor.execute('''
  392. CREATE TABLE IF NOT EXISTS sessions(
  393. user_id INTEGER NOT NULL,
  394. session_id STRING NOT NULL,
  395. FOREIGN KEY (user_id) REFERENCES users(rowid)
  396. )
  397. ''')
  398. cursor.execute('''
  399. CREATE TABLE IF NOT EXISTS orders(
  400. ownership_id INTEGER NOT NULL,
  401. buy BOOLEAN NOT NULL,
  402. "limit" CURRENCY,
  403. stop_loss BOOLEAN,
  404. ordered_amount CURRENCY NOT NULL,
  405. executed_amount CURRENCY DEFAULT 0 NOT NULL,
  406. expiry_dt DATETIME NOT NULL,
  407. FOREIGN KEY (ownership_id) REFERENCES ownership(rowid)
  408. )
  409. ''')
  410. cursor.execute('''
  411. CREATE TABLE IF NOT EXISTS order_history(
  412. ownership_id INTEGER NOT NULL,
  413. buy BOOLEAN NOT NULL,
  414. "limit" CURRENCY,
  415. ordered_amount CURRENCY NOT NULL,
  416. executed_amount CURRENCY NOT NULL,
  417. expiry_dt DATETIME NOT NULL,
  418. status VARCHAR(20) NOT NULL,
  419. order_id INTEGER NOT NULL, -- order_id is not a FOREIGN KEY since orders are deleted from order table afterwards
  420. archived_dt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  421. FOREIGN KEY (ownership_id) REFERENCES ownership(rowid)
  422. )
  423. ''')
  424. cursor.execute('''
  425. CREATE TABLE IF NOT EXISTS transactions(
  426. dt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  427. price CURRENCY NOT NULL,
  428. ownable_id INTEGER NOT NULL,
  429. amount CURRENCY NOT NULL,
  430. FOREIGN KEY (ownable_id) REFERENCES ownables(rowid)
  431. )
  432. ''')
  433. _add_column_if_not_exists(cursor, '''
  434. -- there is a not null constraint for new values that is watched by triggers
  435. ALTER TABLE transactions ADD COLUMN buyer_id INTEGER REFERENCES users(rowid)
  436. ''')
  437. _add_column_if_not_exists(cursor, '''
  438. -- there is a not null constraint for new values that is watched by triggers
  439. ALTER TABLE transactions ADD COLUMN seller_id INTEGER REFERENCES users(rowid)
  440. ''')
  441. cursor.execute('''
  442. CREATE TABLE IF NOT EXISTS keys(
  443. key STRING UNIQUE NOT NULL,
  444. used_by_user_id INTEGER,
  445. FOREIGN KEY (used_by_user_id) REFERENCES users(rowid)
  446. )
  447. ''')
  448. cursor.execute('''
  449. CREATE TABLE IF NOT EXISTS news(
  450. dt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  451. title VARCHAR(50) NOT NULL
  452. )
  453. ''')
  454. cursor.execute('''
  455. CREATE TABLE IF NOT EXISTS banks(
  456. user_id NOT NULL REFERENCES users(rowid)
  457. )
  458. ''')
  459. cursor.execute('''
  460. CREATE TABLE IF NOT EXISTS global_control_values(
  461. dt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  462. value_name VARCHAR NOT NULL,
  463. value FLOAT NOT NULL,
  464. UNIQUE (value_name, dt)
  465. )
  466. ''')
  467. def _add_column_if_not_exists(cursor, query):
  468. if 'ALTER TABLE' not in query.upper():
  469. raise ValueError('Only alter table queries allowed.')
  470. if 'ADD COLUMN' not in query.upper():
  471. raise ValueError('Only add column queries allowed.')
  472. try:
  473. cursor.execute(query)
  474. except OperationalError as e: # if the column already exists this will happen
  475. if 'duplicate column name' not in e.args[0]:
  476. raise