DiffStorage::defineOptionDefaults()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 11
rs 9.4285
cc 3
eloc 7
nc 4
nop 1
1
<?php
2
namespace DataDiff;
3
4
use DataDiff\Exceptions\EmptySchemaException;
5
use Exception;
6
use PDO;
7
8
/**
9
 * @package DataDiff
10
 */
11
abstract class DiffStorage implements DiffStorageInterface {
12
	/** @var PDO */
13
	private $pdo = null;
14
	/** @var DiffStorageStore */
15
	private $storeA = null;
16
	/** @var DiffStorageStore */
17
	private $storeB = null;
18
	/** @var array */
19
	private $keys = [];
20
	/** @var array */
21
	private $valueKeys = [];
22
23
	/**
24
	 * Predefined types:
25
	 *     - integer
26
	 *     - string
27
	 *     - bool
28
	 *     - float
29
	 *     - double
30
	 *     - money
31
	 *
32
	 * @param array $keySchema
33
	 * @param array $valueSchema
34
	 * @param array $options
35
	 */
36
	public function __construct(array $keySchema, array $valueSchema, array $options) {
37
		$options = $this->defineOptionDefaults($options);
38
		$this->pdo = new PDO($options['dsn'], null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
39
		$this->initSqlite();
40
		$this->compatibility();
41
		$this->buildTables();
42
43
		$this->keys = array_keys($keySchema);
44
		$this->valueKeys = array_keys($valueSchema);
45
46
		$sqlKeySchema = $this->buildSchema($keySchema);
47
		$sqlValueSchema = $this->buildSchema($valueSchema);
48
49
		$keyConverter = $this->buildConverter($keySchema);
50
		$valueConverter = $this->buildConverter($valueSchema);
51
		$converter = array_merge($keyConverter, $valueConverter);
52
53
		$this->storeA = new DiffStorageStore($this->pdo, $sqlKeySchema, $sqlValueSchema, $this->keys, $this->valueKeys, $converter, 'a', 'b', $options['duplicate_key_handler']);
54
		$this->storeB = new DiffStorageStore($this->pdo, $sqlKeySchema, $sqlValueSchema, $this->keys, $this->valueKeys, $converter, 'b', 'a', $options['duplicate_key_handler']);
55
	}
56
57
	/**
58
	 * @return array
59
	 */
60
	public function getKeys() {
61
		return $this->keys;
62
	}
63
64
	/**
65
	 * @return DiffStorageStore
66
	 */
67
	public function storeA() {
68
		return $this->storeA;
69
	}
70
71
	/**
72
	 * @return DiffStorageStore
73
	 */
74
	public function storeB() {
75
		return $this->storeB;
76
	}
77
78
	/**
79
	 * @param array $schema
80
	 * @return string
81
	 * @throws Exception
82
	 */
83
	private function buildSchema($schema) {
84
		$def = [];
85
		foreach($schema as $name => $type) {
86
			switch ($type) {
87
				case 'BOOL':
88
					$def[] = sprintf('CASE WHEN CAST(:'.$name.' AS INT) = 0 THEN \'false\' ELSE \'true\' END');
89
					break;
90
				case 'INT':
91
					$def[] = 'printf("%d", :'.$name.')';
92
					break;
93
				case 'FLOAT':
94
					$def[] = 'printf("%0.6f", :'.$name.')';
95
					break;
96
				case 'DOUBLE':
97
					$def[] = 'printf("%0.12f", :'.$name.')';
98
					break;
99
				case 'MONEY':
100
					$def[] = 'printf("%0.2f", :'.$name.')';
101
					break;
102
				case 'STRING':
103
					$def[] = '\'"\'||HEX(TRIM(:'.$name.'))||\'"\'';
104
					break;
105
				case 'MD5':
106
					$def[] = '\'"\'||md5(:'.$name.')||\'"\'';
107
					break;
108
			}
109
		}
110
		if(!count($def)) {
111
			throw new EmptySchemaException('Can\'t operate with empty schema');
112
		}
113
		return join('||"|"||', $def);
114
	}
115
116
	/**
117
	 * @param array $schema
118
	 * @return array
119
	 * @throws Exception
120
	 */
121
	private function buildConverter($schema) {
122
		$def = [];
123
		foreach($schema as $name => $type) {
124
			switch ($type) {
125
				case 'BOOL':
126
					$def[$name] = 'boolval';
127
					break;
128
				case 'INT':
129
					$def[$name] = 'intval';
130
					break;
131
				case 'FLOAT':
132
					$def[$name] = function ($value) { return number_format((float) $value, 6, '.', ''); };
133
					break;
134
				case 'DOUBLE':
135
					$def[$name] = function ($value) { return number_format((float) $value, 12, '.', ''); };
136
					break;
137
				case 'MONEY':
138
					$def[$name] = function ($value) { return number_format((float) $value, 2, '.', ''); };
139
					break;
140
				case 'STRING':
141
					$def[$name] = function ($value) { return (string) $value; };
142
					break;
143
				case 'MD5':
144
					$def[$name] = function ($value) { return md5((string) $value); };
145
					break;
146
			}
147
		}
148
		return $def;
149
	}
150
151
	/**
152
	 */
153
	private function compatibility() {
154
		if(!$this->testStatement('SELECT printf("%0.2f", 19.99999) AS res')) {
155
			$this->registerUDFunction('printf', 'sprintf');
156
		}
157
158
		if(!$this->testStatement('SELECT md5("aaa") AS md5res')) {
159
			$this->registerUDFunction('md5', 'md5');
160
		}
161
	}
162
163
	/**
164
	 * @param string $query
165
	 * @return bool
166
	 */
167
	private function testStatement($query) {
168
		try {
169
			return $this->pdo->query($query)->execute() !== false;
170
		} catch (\PDOException $e) {
171
			return false;
172
		}
173
	}
174
175
	/**
176
	 * @param string $name
177
	 * @param mixed $callback
178
	 * @throws Exception
179
	 */
180
	private function registerUDFunction($name, $callback) {
181
		if(!method_exists($this->pdo, 'sqliteCreateFunction')) {
182
			throw new Exception('It is not possible to create user defined functions for rkr/data-diff\'s sqlite instance');
183
		}
184
		call_user_func([$this->pdo, 'sqliteCreateFunction'], $name, $callback);
185
	}
186
187
	/**
188
	 */
189
	private function initSqlite() {
190
		$tryThis = function ($query) {
191
			try {
192
				$this->pdo->exec($query);
193
			} catch (Exception $e) {
194
				// If the execution failed, go on anyways
195
			}
196
		};
197
		$tryThis("PRAGMA synchronous=OFF");
198
		$tryThis("PRAGMA count_changes=OFF");
199
		$tryThis("PRAGMA journal_mode=MEMORY");
200
		$tryThis("PRAGMA temp_store=MEMORY");
201
	}
202
203
	/**
204
	 */
205
	private function buildTables() {
206
		$this->pdo->exec('CREATE TABLE data_store (s_ab TEXT, s_key TEXT, s_value TEXT, s_data TEXT, s_sort INT, PRIMARY KEY(s_ab, s_key))');
207
		$this->pdo->exec('CREATE INDEX data_store_ab_index ON data_store (s_ab, s_key)');
208
		$this->pdo->exec('CREATE INDEX data_store_key_index ON data_store (s_key)');
209
	}
210
211
	/**
212
	 * @param array $options
213
	 * @return array
214
	 */
215
	private function defineOptionDefaults($options) {
216
		if(!array_key_exists('dsn', $options)) {
217
			$options['dsn'] = 'sqlite::memory:';
218
		}
219
		if(!array_key_exists('duplicate_key_handler', $options)) {
220
			$options['duplicate_key_handler'] = function (array $newData = null, array $oldData = null) {
221
				return array_merge($oldData, $newData);
222
			};
223
		}
224
		return $options;
225
	}
226
}
227