PageRenderTime 38ms CodeModel.GetById 7ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/dbstruct/SpotStruct_pgsql.php

https://github.com/elstenaar86/spotweb
PHP | 433 lines | 299 code | 58 blank | 76 comment | 37 complexity | 916eaec8ab4d771b6de3a3c8954c540d MD5 | raw file
Possible License(s): BSD-3-Clause, GPL-2.0, MPL-2.0-no-copyleft-exception, 0BSD, Apache-2.0
  1. <?php
  2. class SpotStruct_pgsql extends SpotStruct_abs {
  3. /*
  4. * Optimize / analyze (database specific) a number of hightraffic
  5. * tables.
  6. * This function does not modify any schema or data
  7. */
  8. function analyze() {
  9. $this->_dbcon->rawExec("VACUUM ANALYZE spots");
  10. $this->_dbcon->rawExec("VACUUM ANALYZE spotsfull");
  11. $this->_dbcon->rawExec("VACUUM ANALYZE commentsxover");
  12. $this->_dbcon->rawExec("VACUUM ANALYZE commentsfull");
  13. $this->_dbcon->rawExec("VACUUM ANALYZE sessions");
  14. $this->_dbcon->rawExec("VACUUM ANALYZE filters");
  15. $this->_dbcon->rawExec("VACUUM ANALYZE spotteridblacklist");
  16. $this->_dbcon->rawExec("VACUUM ANALYZE filtercounts");
  17. $this->_dbcon->rawExec("VACUUM ANALYZE spotstatelist");
  18. $this->_dbcon->rawExec("VACUUM ANALYZE users");
  19. $this->_dbcon->rawExec("VACUUM ANALYZE cache");
  20. } # analyze
  21. /*
  22. * Converts a 'spotweb' internal datatype to a
  23. * database specific datatype
  24. */
  25. function swDtToNative($colType) {
  26. switch(strtoupper($colType)) {
  27. case 'INTEGER' : $colType = 'integer'; break;
  28. case 'UNSIGNED INTEGER' : $colType = 'bigint'; break;
  29. case 'BIGINTEGER' : $colType = 'bigint'; break;
  30. case 'UNSIGNED BIGINTEGER' : $colType = 'bigint'; break;
  31. case 'BOOLEAN' : $colType = 'boolean'; break;
  32. case 'MEDIUMBLOB' : $colType = 'bytea'; break;
  33. } # switch
  34. return $colType;
  35. } # swDtToNative
  36. /*
  37. * Converts a database native datatype to a spotweb native
  38. * datatype
  39. */
  40. function nativeDtToSw($colInfo) {
  41. switch(strtolower($colInfo)) {
  42. case 'integer' : $colInfo = 'INTEGER'; break;
  43. case 'bigint' : $colInfo = 'BIGINTEGER'; break;
  44. case 'boolean' : $colInfo = 'BOOLEAN'; break;
  45. case 'bytea' : $colInfo = 'MEDIUMBLOB'; break;
  46. } # switch
  47. return $colInfo;
  48. } # nativeDtToSw
  49. /* checks if an index exists */
  50. function indexExists($idxname, $tablename) {
  51. $q = $this->_dbcon->arrayQuery("SELECT indexname FROM pg_indexes WHERE schemaname = CURRENT_SCHEMA() AND tablename = '%s' AND indexname = '%s'",
  52. Array($tablename, $idxname));
  53. return !empty($q);
  54. } # indexExists
  55. /* checks if a column exists */
  56. function columnExists($tablename, $colname) {
  57. $q = $this->_dbcon->arrayQuery("SELECT column_name FROM information_schema.columns
  58. WHERE table_schema = CURRENT_SCHEMA() AND table_name = '%s' AND column_name = '%s'",
  59. Array($tablename, $colname));
  60. return !empty($q);
  61. } # columnExists
  62. /* checks if a fts text index exists */
  63. function ftsExists($ftsname, $tablename, $colList) {
  64. foreach($colList as $num => $col) {
  65. $indexInfo = $this->getIndexInfo($ftsname . '_' . $num, $tablename);
  66. if ((empty($indexInfo)) || (strtolower($indexInfo[0]['column_name']) != strtolower($col))) {
  67. return false;
  68. } # if
  69. } # foreach
  70. return true;
  71. } # ftsExists
  72. /* creates a full text index */
  73. function createFts($ftsname, $tablename, $colList) {
  74. foreach($colList as $num => $col) {
  75. $indexInfo = $this->getIndexInfo($ftsname . '_' . $num, $tablename);
  76. if ((empty($indexInfo)) || (strtolower($indexInfo[0]['column_name']) != strtolower($col))) {
  77. $this->dropIndex($ftsname . '_' . $num, $tablename);
  78. $this->addIndex($ftsname . '_' . $num, 'FULLTEXT', $tablename, array($col));
  79. } # if
  80. } # foreach
  81. } # createFts
  82. /* drops a fulltext index */
  83. function dropFts($ftsname, $tablename, $colList) {
  84. foreach($colList as $num => $col) {
  85. $this->dropIndex($ftsname . '_' . $num, $tablename);
  86. } # foreach
  87. } # dropFts
  88. /* returns FTS info */
  89. function getFtsInfo($ftsname, $tablename, $colList) {
  90. $ftsList = array();
  91. foreach($colList as $num => $col) {
  92. $tmpIndex = $this->getIndexInfo($ftsname . '_' . $num, $tablename);
  93. if (!empty($tmpIndex)) {
  94. $ftsList[] = $tmpIndex[0];
  95. } # if
  96. } # foreach
  97. return $ftsList;
  98. } # getFtsInfo
  99. /*
  100. * Adds an index, but first checks if the index doesn't
  101. * exist already.
  102. *
  103. * $idxType can be either 'UNIQUE', '' or 'FULLTEXT'
  104. */
  105. function addIndex($idxname, $idxType, $tablename, $colList) {
  106. if (!$this->indexExists($idxname, $tablename)) {
  107. switch($idxType) {
  108. case 'UNIQUE': {
  109. $this->_dbcon->rawExec("CREATE UNIQUE INDEX " . $idxname . " ON " . $tablename . "(" . implode(",", $colList) . ")");
  110. break;
  111. } # case
  112. case 'FULLTEXT' : {
  113. $this->_dbcon->rawExec("CREATE INDEX " . $idxname . " ON " . $tablename . " USING gin(to_tsvector('dutch', " . implode(",", $colList) . "))");
  114. break;
  115. } # case
  116. default : {
  117. $this->_dbcon->rawExec("CREATE INDEX " . $idxname . " ON " . $tablename . "(" . implode(",", $colList) . ")");
  118. } # default
  119. } # switch
  120. } # if
  121. } # addIndex
  122. /* drops an index if it exists */
  123. function dropIndex($idxname, $tablename) {
  124. /*
  125. * Make sure the table exists, else this will return an error
  126. * and return a fatal
  127. */
  128. if (!$this->tableExists($tablename)) {
  129. return ;
  130. } # if
  131. if ($this->indexExists($idxname, $tablename)) {
  132. $this->_dbcon->rawExec("DROP INDEX " . $idxname);
  133. } # if
  134. } # dropIndex
  135. /* adds a column if the column doesn't exist yet */
  136. function addColumn($colName, $tablename, $colType, $colDefault, $notNull, $collation) {
  137. if (!$this->columnExists($tablename, $colName)) {
  138. # set the DEFAULT value
  139. if (strlen($colDefault) != 0) {
  140. $colDefault = 'DEFAULT ' . $colDefault;
  141. } # if
  142. # Convert the column type to a type we use in PostgreSQL
  143. $colType = $this->swDtToNative($colType);
  144. /*
  145. * Only pgsql 9.1 (only just released) supports per-column collation, so for now
  146. * we ignore this
  147. */
  148. switch(strtolower($collation)) {
  149. case 'utf8' :
  150. case 'ascii' :
  151. case 'ascii_bin' :
  152. case '' : $colSetting = ''; break;
  153. default : throw new Exception("Invalid collation setting");
  154. } # switch
  155. # and define the 'NOT NULL' part
  156. switch($notNull) {
  157. case true : $nullStr = 'NOT NULL'; break;
  158. default : $nullStr = '';
  159. } # switch
  160. $this->_dbcon->rawExec("ALTER TABLE " . $tablename .
  161. " ADD COLUMN " . $colName . " " . $colType . " " . $colSetting . " " . $colDefault . " " . $nullStr);
  162. } # if
  163. } # addColumn
  164. /* alters a column - does not check if the column doesn't adhere to the given definition */
  165. function modifyColumn($colName, $tablename, $colType, $colDefault, $notNull, $collation, $what) {
  166. # set the DEFAULT value
  167. if (strlen($colDefault) != 0) {
  168. $colDefault = 'DEFAULT ' . $colDefault;
  169. } # if
  170. # Convert the column type to a type we use in PostgreSQL
  171. $colType = $this->swDtToNative($colType);
  172. /*
  173. * Only pgsql 9.1 (only just released) supports per-column collation, so for now
  174. * we ignore this
  175. */
  176. switch(strtolower($collation)) {
  177. case 'utf8' :
  178. case 'ascii' :
  179. case 'ascii_bin' :
  180. case '' : $colSetting = ''; break;
  181. default : throw new Exception("Invalid collation setting");
  182. } # switch
  183. # and define the 'NOT NULL' part
  184. switch($notNull) {
  185. case true : $nullStr = 'NOT NULL'; break;
  186. default : $nullStr = '';
  187. } # switch
  188. # Alter the column type
  189. $this->_dbcon->rawExec("ALTER TABLE " . $tablename . " ALTER COLUMN " . $colName . " TYPE " . $colType);
  190. # Change the default value (if one set, else drop it)
  191. if (strlen($colDefault) > 0) {
  192. $this->_dbcon->rawExec("ALTER TABLE " . $tablename . " ALTER COLUMN " . $colName . " SET " . $colDefault);
  193. } else {
  194. $this->_dbcon->rawExec("ALTER TABLE " . $tablename . " ALTER COLUMN " . $colName . " DROP DEFAULT");
  195. } # if
  196. # and changes the null/not-null constraint
  197. if (strlen($notNull) > 0) {
  198. $this->_dbcon->rawExec("ALTER TABLE " . $tablename . " ALTER COLUMN " . $colName . " SET NOT NULL");
  199. } else {
  200. $this->_dbcon->rawExec("ALTER TABLE " . $tablename . " ALTER COLUMN " . $colName . " DROP NOT NULL");
  201. } # if
  202. } # modifyColumn
  203. /* drops a column */
  204. function dropColumn($colName, $tablename) {
  205. if ($this->columnExists($tablename, $colName)) {
  206. $this->_dbcon->rawExec("ALTER TABLE " . $tablename . " DROP COLUMN " . $colName);
  207. } # if
  208. } # dropColumn
  209. /* checks if a table exists */
  210. function tableExists($tablename) {
  211. $q = $this->_dbcon->arrayQuery("SELECT tablename FROM pg_tables WHERE schemaname = CURRENT_SCHEMA() AND (tablename = '%s')", array($tablename));
  212. return !empty($q);
  213. } # tableExists
  214. /* creates an empty table with only an ID field. Collation should be either UTF8 or ASCII */
  215. function createTable($tablename, $collation) {
  216. if (!$this->tableExists($tablename)) {
  217. /*
  218. * Only pgsql 9.1 (only just released) supports per-column collation, so for now
  219. * we ignore this
  220. */
  221. switch(strtolower($collation)) {
  222. case 'utf8' :
  223. case 'ascii' :
  224. case '' : $colSetting = ''; break;
  225. default : throw new Exception("Invalid collation setting");
  226. } # switch
  227. $this->_dbcon->rawExec("CREATE TABLE " . $tablename . " (id SERIAL PRIMARY KEY) " . $colSetting);
  228. } # if
  229. } # createTable
  230. /* drop a table */
  231. function dropTable($tablename) {
  232. if ($this->tableExists($tablename)) {
  233. $this->_dbcon->rawExec("DROP TABLE " . $tablename);
  234. } # if
  235. } # dropTable
  236. /* dummy - postgresql doesn't know storage engines of course */
  237. function alterStorageEngine($tablename, $engine) {
  238. return false;
  239. } # alterStorageEngine
  240. /* rename a table */
  241. function renameTable($tablename, $newTableName) {
  242. $this->_dbcon->rawExec("ALTER TABLE " . $tablename . " RENAME TO " . $newTableName);
  243. } # renameTable
  244. /* drop a foreign key constraint */
  245. function dropForeignKey($tablename, $colname, $reftable, $refcolumn, $action) {
  246. /* SQL from http://stackoverflow.com/questions/1152260/postgres-sql-to-list-table-foreign-keys */
  247. $q = $this->_dbcon->arrayQuery("SELECT
  248. tc.constraint_name AS CONSTRAINT_NAME,
  249. tc.table_name AS TABLE_NAME,
  250. tc.constraint_schema AS TABLE_SCHEMA,
  251. kcu.column_name AS COLUMN_NAME,
  252. ccu.table_name AS REFERENCED_TABLE_NAME,
  253. ccu.column_name AS REFERENCED_COLUMN_NAME
  254. FROM
  255. information_schema.table_constraints AS tc
  256. JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
  257. JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
  258. WHERE constraint_type = 'FOREIGN KEY'
  259. AND tc.TABLE_SCHEMA = CURRENT_SCHEMA()
  260. AND tc.TABLE_NAME = '%s'
  261. AND kcu.COLUMN_NAME = '%s'
  262. AND ccu.table_name = '%s'
  263. AND ccu.column_name = '%s'",
  264. Array($tablename, $colname, $reftable, $refcolumn));
  265. if (!empty($q)) {
  266. foreach($q as $res) {
  267. $this->_dbcon->rawExec("ALTER TABLE " . $tablename . " DROP CONSTRAINT " . $res['constraint_name']);
  268. } # foreach
  269. } # if
  270. } # dropForeignKey
  271. /* create a foreign key constraint */
  272. function addForeignKey($tablename, $colname, $reftable, $refcolumn, $action) {
  273. /* SQL from http://stackoverflow.com/questions/1152260/postgres-sql-to-list-table-foreign-keys */
  274. $q = $this->_dbcon->arrayQuery("SELECT
  275. tc.constraint_name AS CONSTRAINT_NAME,
  276. tc.table_name AS TABLE_NAME,
  277. tc.constraint_schema AS TABLE_SCHEMA,
  278. kcu.column_name AS COLUMN_NAME,
  279. ccu.table_name AS REFERENCED_TABLE_NAME,
  280. ccu.column_name AS REFERENCED_COLUMN_NAME
  281. FROM
  282. information_schema.table_constraints AS tc
  283. JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
  284. JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
  285. WHERE constraint_type = 'FOREIGN KEY'
  286. AND tc.TABLE_SCHEMA = CURRENT_SCHEMA()
  287. AND tc.TABLE_NAME = '%s'
  288. AND kcu.COLUMN_NAME = '%s'
  289. AND ccu.table_name = '%s'
  290. AND ccu.column_name = '%s'",
  291. Array($tablename, $colname, $reftable, $refcolumn));
  292. if (empty($q)) {
  293. $this->_dbcon->rawExec("ALTER TABLE " . $tablename . " ADD FOREIGN KEY (" . $colname . ")
  294. REFERENCES " . $reftable . " (" . $refcolumn . ") " . $action);
  295. } # if
  296. } # addForeignKey
  297. /* Returns in a fixed format, column information */
  298. function getColumnInfo($tablename, $colname) {
  299. $q = $this->_dbcon->arrayQuery("SELECT column_name AS \"COLUMN_NAME\",
  300. column_default AS \"COLUMN_DEFAULT\",
  301. is_nullable AS \"IS_NULLABLE\",
  302. data_type AS \"DATA_TYPE\",
  303. numeric_precision AS \"NUMERIC_PRECISION\",
  304. CASE
  305. WHEN (data_type = 'character varying') THEN 'varchar(' || character_maximum_length || ')'
  306. WHEN (data_type = 'integer') THEN 'integer'
  307. WHEN (data_type = 'bigint') THEN 'bigint'
  308. WHEN (data_type = 'boolean') THEN 'boolean'
  309. WHEN (data_type = 'text') THEN 'text'
  310. WHEN (data_type = 'bytea') THEN 'bytea'
  311. END as \"COLUMN_TYPE\",
  312. character_set_name AS \"CHARACTER_SET_NAME\",
  313. collation_name AS \"COLLATION_NAME\"
  314. FROM information_schema.COLUMNS
  315. WHERE TABLE_SCHEMA = CURRENT_SCHEMA()
  316. AND TABLE_NAME = '%s'
  317. AND COLUMN_NAME = '%s'",
  318. Array($tablename, $colname));
  319. if (!empty($q)) {
  320. $q = $q[0];
  321. $q['NOTNULL'] = ($q['IS_NULLABLE'] != 'YES');
  322. # a default value has to given, so make it compareable to what we define
  323. if ((strlen($q['COLUMN_DEFAULT']) == 0) && (is_string($q['COLUMN_DEFAULT']))) {
  324. $q['COLUMN_DEFAULT'] = "''";
  325. } # if
  326. /*
  327. * PostgreSQL per default explicitly typecasts the value, but
  328. * we cannot do this, so we strip the default value of its typecast
  329. */
  330. if (strpos($q['COLUMN_DEFAULT'], ':') !== false) {
  331. $elems = explode(':', $q['COLUMN_DEFAULT']);
  332. $q['COLUMN_DEFAULT'] = $elems[0];
  333. } # if
  334. } # if
  335. return $q;
  336. } # getColumnInfo
  337. /* Returns in a fixed format, index information */
  338. function getIndexInfo($idxname, $tablename) {
  339. $q = $this->_dbcon->arrayQuery("SELECT *
  340. FROM pg_indexes
  341. WHERE schemaname = CURRENT_SCHEMA()
  342. AND tablename = '%s'
  343. AND indexname = '%s'", Array($tablename, $idxname));
  344. if (empty($q)) {
  345. return array();
  346. } # if
  347. # a index name has to be unique
  348. $q = $q[0];
  349. # is the index marked as unique
  350. $tmpAr = explode(" ", $q['indexdef']);
  351. $isNotUnique = (strtolower($tmpAr[1]) != 'unique');
  352. # retrieve the column list and seperate the column definition per comma
  353. preg_match_all("/\((.*)\)/", $q['indexdef'], $tmpAr);
  354. $colList = explode(",", $tmpAr[1][0]);
  355. $colList = array_map('trim', $colList);
  356. # gin indexes (fulltext search) only have 1 column, so we excempt them
  357. $idxInfo = array();
  358. if (stripos($tmpAr[1][0], 'to_tsvector') === false) {
  359. for($i = 0; $i < count($colList); $i++) {
  360. $idxInfo[] = array('column_name' => $colList[$i],
  361. 'non_unique' => (int) $isNotUnique,
  362. 'index_type' => 'BTREE'
  363. );
  364. } # foreach
  365. } else {
  366. # extract the column name
  367. preg_match_all("/\((.*)\)/U", $colList[1], $tmpAr);
  368. # and create the index info
  369. $idxInfo[] = array('column_name' => $tmpAr[1][0],
  370. 'non_unique' => (int) $isNotUnique,
  371. 'index_type' => 'FULLTEXT');
  372. } # else
  373. return $idxInfo;
  374. } # getIndexInfo
  375. } # class