Passed
Push — master ( 66acfb...380864 )
by Richard
01:44
created

SqliteAdapter::createTable()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 3
nop 3
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
1
<?php
2
namespace Maphper\DataSource;
3
4
class SqliteAdapter implements DatabaseAdapter {
5
	private $pdo;
6
	private $stmtCache;
7
8
	public function __construct(\PDO $pdo) {
9
		$this->pdo = $pdo;
10
        $this->stmtCache = new StmtCache($pdo);
11
	}
12
13
	public function quote($str) {
14
		return '`' . str_replace('.', '`.`', trim($str, '`')) . '`';
15
	}
16
17
	public function query(\Maphper\Lib\Query $query) {
18
        $stmt = $this->stmtCache->getCachedStmt($query->getSql());
19
		$args = $query->getArgs();
20
21
        //Handle SQLite when PDO_ERRMODE is set to SILENT
22
        if ($stmt === false) throw new \Exception('Invalid query');
23
24
        $stmt->execute($args);
25
        if ($stmt->errorCode() !== '00000' && $stmt->errorInfo()[2] == 'database schema has changed') {
26
			$this->stmtCache->deleteQueryFromCache($query->getSql());
27
			return $this->query($query);
28
        }
29
30
        return $stmt;
31
	}
32
	
33
	public function lastInsertId() {
34
		return $this->pdo->lastInsertId();
35
	}
36
	
37
	private function getType($val) {
38
		if ($val instanceof \DateTime) return 'DATETIME';
39
		else if (is_int($val)) return  'INTEGER';
40
		else if (is_double($val)) return 'DECIMAL(9,' . strlen($val) - strrpos($val, '.') - 1 . ')';
41
		else if (is_string($val) && strlen($val) < 256) return 'VARCHAR(255)';
42
		else if (is_string($val) && strlen($val) > 256) return 'LONGBLOG';
43
		else return 'VARCHAR(255)';		
44
	}
45
46
	private function tableExists($name) {
47
		$result = $this->pdo->query('SELECT name FROM sqlite_master WHERE type="table" and name="'. $name.'"');
48
		return count($result->fetchAll()) == 1;
49
	}
50
51
	private function getColumns($table) {
52
		$result = $this->pdo->query('PRAGMA table_info(' . $table . ');')->fetchAll(\PDO::FETCH_OBJ);
53
		$return = [];
54
		foreach ($result as $row) {
55
			$return[] = $row->name;
56
		}
57
		return $return;
58
	}
59
60
	//Alter the database so that it can store $data
61
	public function alterDatabase($table, array $primaryKey, $data) {
62
		//Unset query cache, otherwise it causes:
63
		// SQLSTATE[HY000]: General error: 17 database schema has changed
64
		$this->stmtCache->clearCache();
65
66
        // Create temp table to create a new structure
67
		$affix = '_'.substr(md5($table), 0, 6);
68
        $tempTable = $table . $affix;
69
		$this->createTable($tempTable, $primaryKey, $data);
70
        $this->alterColumns($tempTable, $primaryKey, $data);
71
		$this->copyTableData($table, $tempTable);
72
73
		$this->pdo->query('DROP TABLE IF EXISTS ' . $table );
74
		$this->pdo->query('ALTER TABLE ' . $tempTable . ' RENAME TO '. $table );
75
76
	}
77
78
    private function copyTableData($tableFrom, $tableTo) {
79
        try {
80
			if ($this->tableExists($tableFrom)) {
81
				$columns = implode(', ', $this->getColumns($tableFrom));
82
83
				$this->pdo->query('INSERT INTO ' . $this->quote($tableTo) . '(' . $columns . ') SELECT ' . $columns . ' FROM ' . $this->quote($tableFrom));
84
			}
85
		}
86
		catch (\PDOException $e) {
87
			// No data to copy
88
			echo $e->getMessage();
89
		}
90
    }
91
92
	private function createTable($table, array $primaryKey, $data) {
93
		$parts = [];
94
		foreach ($primaryKey as $key) {
95
			$pk = $data->$key;
96
			if ($pk == null) $parts[] = $key . ' INTEGER'; 
97
			else $parts[] = $key . ' ' . $this->getType($pk) . ' NOT NULL';					
98
		}
99
		
100
		$pkField = implode(', ', $parts) . ', PRIMARY KEY(' . implode(', ', $primaryKey) . ')';
101
102
		$this->pdo->query('CREATE TABLE ' . $table . ' (' . $pkField . ')');
103
	}
104
105
    private function alterColumns($table, array $primaryKey, $data) {
106
        foreach ($data as $key => $value) {
107
			if ($this->isNotSavableType($value, $key, $primaryKey)) continue;
108
109
			$type = $this->getType($value);
110
		
111
			$this->pdo->query('ALTER TABLE ' . $table . ' ADD ' . $this->quote($key) . ' ' . $type);
112
		}
113
    }
114
115
    private function isNotSavableType($value, $key, $primaryKey) {
116
        return is_array($value) || (is_object($value) && !($value instanceof \DateTime)) ||
117
                in_array($key, $primaryKey);
118
    }
119
120
	public function addIndex($table, array $fields) {
121
		if (empty($fields)) return false;
122
		
123
		//SQLite doesn't support ASC/DESC indexes, remove the keywords
124
		foreach ($fields as &$field) $field = str_ireplace([' desc', ' asc'], '', $field);
125
		sort($fields);
126
		$fields = array_map('strtolower', $fields);
127
		$fields = array_map('trim', $fields);
128
		$keyName = implode('_', $fields);
129
	
130
		
131
		try {
132
			$this->pdo->query('CREATE INDEX IF NOT EXISTS  ' . $keyName . ' ON ' . $table . ' (' . implode(', ', $fields) . ')');
133
		}
134
		catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
135
			
136
		}
137
	}
138
	
139
	public function optimiseColumns($table) {
140
		//TODO
141
	}
142
}