1: <?php
2: namespace phpcassa;
3:
4: use phpcassa\Connection\ConnectionWrapper;
5: use phpcassa\Schema\DataType;
6:
7: use cassandra\KsDef;
8: use cassandra\CfDef;
9: use cassandra\ColumnDef;
10: use cassandra\IndexType;
11:
12: /**
13: * Helps with getting information about the schema, making
14: * schema changes, and getting information about the state
15: * and configuration of the cluster.
16: *
17: * @package phpcassa
18: */
19: class SystemManager {
20:
21: /** @internal */
22: const KEEP = "<__keep__>";
23:
24: /**
25: * @param string $server the host and port to connect to, in the
26: * form 'host:port'. Defaults to 'localhost:9160'.
27: * @param array $credentials if using authentication or authorization with Cassandra,
28: * a username and password need to be supplied. This should be in the form
29: * array("username" => username, "password" => password)
30: * @param int $send_timeout the socket send timeout in milliseconds. Defaults to 15000.
31: * @param int $recv_timeout the socket receive timeout in milliseconds. Defaults to 15000.
32: */
33: public function __construct($server='localhost:9160',
34: $credentials=NULL,
35: $send_timeout=15000,
36: $recv_timeout=15000)
37: {
38: $this->conn = new ConnectionWrapper(
39: NULL, $server, $credentials, True,
40: $send_timeout, $recv_timeout);
41: $this->client = $this->conn->client;
42: }
43:
44: /**
45: * Closes the underlying Thrift connection.
46: */
47: public function close() {
48: $this->conn->close();
49: }
50:
51: protected function wait_for_agreement() {
52: while (true) {
53: $versions = $this->client->describe_schema_versions();
54: if (count($versions) == 1)
55: break;
56: usleep(500);
57: }
58: }
59:
60: /**
61: * Creates a new keyspace.
62: *
63: * Example usage:
64: * <code>
65: * use phpcassa\SystemManager;
66: * use phpcassa\Schema\StrategyClass;
67: *
68: * $sys = SystemManager();
69: * $attrs = array("strategy_class" => StrategyClass\SIMPLE_STRATEGY,
70: * "strategy_options" => array("replication_factor" => "1"));
71: * $sys->create_keyspace("Keyspace1", $attrs);
72: * </code>
73: *
74: * @param string $keyspace the keyspace name
75: * @param array $attrs an array that maps attribute
76: * names to values. Valid attribute names include
77: * "strategy_class", "strategy_options", and
78: * "replication_factor".
79: *
80: * By default, SimpleStrategy will be used with a replication
81: * factor of 1 and no strategy options.
82: *
83: */
84: public function create_keyspace($keyspace, $attrs) {
85: $ksdef = $this->make_ksdef($keyspace, $attrs);
86: $this->client->system_add_keyspace($ksdef);
87: $this->wait_for_agreement();
88: }
89:
90: /**
91: * Modifies a keyspace's properties.
92: *
93: * Example usage:
94: * <code>
95: * $sys = SystemManager();
96: * $attrs = array("replication_factor" => 2);
97: * $sys->alter_keyspace("Keyspace1", $attrs);
98: * </code>
99: *
100: * @param string $keyspace the keyspace to modify
101: * @param array $attrs an array that maps attribute
102: * names to values. Valid attribute names include
103: * "strategy_class", "strategy_options", and
104: * "replication_factor".
105: *
106: */
107: public function alter_keyspace($keyspace, $attrs) {
108: $ksdef = $this->client->describe_keyspace($keyspace);
109: $ksdef = $this->make_ksdef($keyspace, $attrs, $ksdef);
110: $this->client->system_update_keyspace($ksdef);
111: $this->wait_for_agreement();
112: }
113:
114: /*
115: * Drops a keyspace.
116: *
117: * @param string $keyspace the keyspace name
118: */
119: public function drop_keyspace($keyspace) {
120: $this->client->system_drop_keyspace($keyspace);
121: $this->wait_for_agreement();
122: }
123:
124: protected static function endswith($haystack, $needle) {
125: $start = strlen($needle) * -1; //negative
126: return (substr($haystack, $start) === $needle);
127: }
128:
129: protected function make_ksdef($name, $attrs, $orig=NULL) {
130: if ($orig !== NULL) {
131: $ksdef = $orig;
132: } else {
133: $ksdef = new KsDef();
134: $ksdef->strategy_class = 'SimpleStrategy';
135: $ksdef->strategy_options = array("replication_factor" => "1");
136: }
137:
138: $ksdef->name = $name;
139: $ksdef->cf_defs = array();
140: foreach ($attrs as $attr => $value) {
141: if ($attr == "strategy_class") {
142: if (strpos($value, ".") === false)
143: $value = "org.apache.cassandra.locator.$value";
144: $ksdef->strategy_class = $value;
145: } else {
146: $ksdef->$attr = $value;
147: }
148: }
149: return $ksdef;
150: }
151:
152: /**
153: * Creates a column family.
154: *
155: * Example usage:
156: * <code>
157: * $sys = SystemManager();
158: * $attrs = array("column_type" => "Standard",
159: * "comparator_type" => "org.apache.cassandra.db.marshal.AsciiType",
160: * "memtable_throughput_in_mb" => 32);
161: * $sys->create_column_family("Keyspace1", "ColumnFamily1", $attrs);
162: * </code>
163: *
164: * @param string $keyspace the keyspace containing the column family
165: * @param string $column_family the name of the column family
166: * @param array $attrs an array that maps attribute
167: * names to values.
168: */
169: public function create_column_family($keyspace, $column_family, $attrs=null) {
170: if ($attrs === null)
171: $attrs = array();
172:
173: $this->client->set_keyspace($keyspace);
174: $cfdef = $this->make_cfdef($keyspace, $column_family, $attrs);
175: $this->client->system_add_column_family($cfdef);
176: $this->wait_for_agreement();
177: }
178:
179: protected function get_cfdef($ksname, $cfname) {
180: $ksdef = $this->client->describe_keyspace($ksname);
181: $cfdefs = $ksdef->cf_defs;
182: foreach($cfdefs as $cfdef) {
183: if ($cfdef->name == $cfname)
184: return $cfdef;
185: }
186: return;
187: }
188:
189: protected function make_cfdef($ksname, $cfname, $attrs, $orig=NULL) {
190: if ($orig !== NULL) {
191: $cfdef = $orig;
192: } else {
193: $cfdef = new CfDef();
194: $cfdef->column_type = "Standard";
195: }
196:
197: $cfdef->keyspace = $ksname;
198: $cfdef->name = $cfname;
199:
200: foreach ($attrs as $attr => $value)
201: $cfdef->$attr = $value;
202:
203: return $cfdef;
204: }
205:
206: /**
207: * Modifies a column family's attributes.
208: *
209: * Example usage:
210: * <code>
211: * $sys = SystemManager();
212: * $attrs = array("max_compaction_threshold" => 10);
213: * $sys->alter_column_family("Keyspace1", "ColumnFamily1", $attrs);
214: * </code>
215: *
216: * @param string $keyspace the keyspace containing the column family
217: * @param string $column_family the name of the column family
218: * @param array $attrs an array that maps attribute
219: * names to values.
220: */
221: public function alter_column_family($keyspace, $column_family, $attrs) {
222: $cfdef = $this->get_cfdef($keyspace, $column_family);
223: $cfdef = $this->make_cfdef($keyspace, $column_family, $attrs, $cfdef);
224: $this->client->set_keyspace($cfdef->keyspace);
225: $this->client->system_update_column_family($cfdef);
226: $this->wait_for_agreement();
227: }
228:
229: /*
230: * Drops a column family from a keyspace.
231: *
232: * @param string $keyspace the keyspace the CF is in
233: * @param string $column_family the column family name
234: */
235: public function drop_column_family($keyspace, $column_family) {
236: $this->client->set_keyspace($keyspace);
237: $this->client->system_drop_column_family($column_family);
238: $this->wait_for_agreement();
239: }
240:
241: /**
242: * Mark the entire column family as deleted.
243: *
244: * From the user's perspective a successful call to truncate will result
245: * complete data deletion from cfname. Internally, however, disk space
246: * will not be immediatily released, as with all deletes in cassandra,
247: * this one only marks the data as deleted.
248: *
249: * The operation succeeds only if all hosts in the cluster at available
250: * and will throw an UnavailableException if some hosts are down.
251: *
252: * Example usage:
253: * <code>
254: * $sys = SystemManager();
255: * $sys->truncate_column_family("Keyspace1", "ColumnFamily1");
256: * </code>
257: *
258: * @param string $keyspace the keyspace the CF is in
259: * @param string $column_family the column family name
260: */
261: public function truncate_column_family($keyspace, $column_family) {
262: $this->client->set_keyspace($keyspace);
263: $this->client->truncate($column_family);
264: }
265:
266: /**
267: * Adds an index to a column family.
268: *
269: * Example usage:
270: *
271: * <code>
272: * $sys = new SystemManager();
273: * $sys->create_index("Keyspace1", "Users", "name", "UTF8Type");
274: * </code>
275: *
276: * @param string $keyspace the name of the keyspace containing the column family
277: * @param string $column_family the name of the column family
278: * @param string $column the name of the column to put the index on
279: * @param string $data_type the data type of the values being indexed
280: * @param string $index_name an optional name for the index
281: * @param IndexType $index_type the type of index. Defaults to
282: * \cassandra\IndexType::KEYS_INDEX, which is currently the only option.
283: */
284: public function create_index($keyspace, $column_family, $column,
285: $data_type=self::KEEP, $index_name=NULL, $index_type=IndexType::KEYS)
286: {
287: $this->_alter_column($keyspace, $column_family, $column,
288: $data_type=$data_type, $index_type=$index_type, $index_name=$index_name);
289: }
290:
291: /**
292: * Drop an index from a column family.
293: *
294: * Example usage:
295: *
296: * <code>
297: * $sys = new SystemManager();
298: * $sys->drop_index("Keyspace1", "Users", "name");
299: * </code>
300: *
301: * @param string $keyspace the name of the keyspace containing the column family
302: * @param string $column_family the name of the column family
303: * @param string $column the name of the column to drop the index from
304: */
305: public function drop_index($keyspace, $column_family, $column) {
306: $this->_alter_column($keyspace, $column_family, $column,
307: $data_type=self::KEEP, $index_type=NULL, $index_name=NULL);
308: }
309:
310: /**
311: * Changes or sets the validation class of a single column.
312: *
313: * Example usage:
314: *
315: * <code>
316: * $sys = new SystemManager();
317: * $sys->alter_column("Keyspace1", "Users", "name", "UTF8Type");
318: * </code>
319: *
320: * @param string $keyspace the name of the keyspace containing the column family
321: * @param string $column_family the name of the column family
322: * @param string $column the name of the column to put the index on
323: * @param string $data_type the data type of the values being indexed
324: */
325: public function alter_column($keyspace, $column_family, $column, $data_type) {
326: $this->_alter_column($keyspace, $column_family, $column, $data_type);
327: }
328:
329: protected static function qualify_class_name($data_type) {
330: if ($data_type === null)
331: return null;
332:
333: if (strpos($data_type, ".") === false)
334: return "org.apache.cassandra.db.marshal.$data_type";
335: else
336: return $data_type;
337: }
338:
339: protected function _alter_column($keyspace, $column_family, $column,
340: $data_type=self::KEEP, $index_type=self::KEEP, $index_name=self::KEEP) {
341:
342: $this->client->set_keyspace($keyspace);
343: $cfdef = $this->get_cfdef($keyspace, $column_family);
344:
345: if ($cfdef->column_type == 'Super') {
346: $col_name_type = DataType::get_type_for($cfdef->subcomparator_type);
347: } else {
348: $col_name_type = DataType::get_type_for($cfdef->comparator_type);
349: }
350: $packed_name = $col_name_type->pack($column);
351:
352: $col_def = null;
353: $col_meta = $cfdef->column_metadata;
354: for ($i = 0; $i < count($col_meta); $i++) {
355: $temp_col_def = $col_meta[$i];
356: if ($temp_col_def->name === $packed_name) {
357: $col_def = $temp_col_def;
358: unset($col_meta[$i]);
359: break;
360: }
361: }
362:
363: if ($col_def === null) {
364: $col_def = new ColumnDef();
365: $col_def->name = $packed_name;
366: }
367: if ($data_type !== self::KEEP)
368: $col_def->validation_class = self::qualify_class_name($data_type);
369: if ($index_type !== self::KEEP)
370: $col_def->index_type = $index_type;
371: if ($index_name !== self::KEEP)
372: $col_def->index_name = $index_name;
373:
374: $col_meta[] = $col_def;
375: $cfdef->column_metadata = $col_meta;
376: $this->client->system_update_column_family($cfdef);
377: $this->wait_for_agreement();
378: }
379:
380: /**
381: * Describes the Cassandra cluster.
382: *
383: * @return array the node to token mapping
384: */
385: public function describe_ring($keyspace) {
386: return $this->client->describe_ring($keyspace);
387: }
388:
389: /**
390: * Gives the cluster name.
391: *
392: * @return string the cluster name
393: */
394: public function describe_cluster_name() {
395: return $this->client->describe_cluster_name();
396: }
397:
398: /**
399: * Gives the Thrift API version for the Cassandra instance.
400: *
401: * Note that this is different than the Cassandra version.
402: *
403: * @return string the API version
404: */
405: public function describe_version() {
406: return $this->client->describe_version();
407: }
408:
409: /**
410: * Describes what schema version each node currently has.
411: * Differences in schema versions indicate a schema conflict.
412: *
413: * @return array a mapping of schema versions to nodes.
414: */
415: public function describe_schema_versions() {
416: return $this->client->describe_schema_versions();
417: }
418:
419: /**
420: * Describes the cluster's partitioner.
421: *
422: * @return string the name of the partitioner in use
423: */
424: public function describe_partitioner() {
425: return $this->client->describe_partitioner();
426: }
427:
428: /**
429: * Describes the cluster's snitch.
430: *
431: * @return string the name of the snitch in use
432: */
433: public function describe_snitch() {
434: return $this->client->describe_snitch();
435: }
436:
437: /**
438: * Returns a description of the keyspace and its column families.
439: * This includes all configuration settings for the keyspace and
440: * column families.
441: *
442: * @param string $keyspace the keyspace name
443: *
444: * @return cassandra\KsDef
445: */
446: public function describe_keyspace($keyspace) {
447: return $this->client->describe_keyspace($keyspace);
448: }
449:
450: /**
451: * Like describe_keyspace(), but for all keyspaces.
452: *
453: * @return array an array of cassandra\KsDef
454: */
455: public function describe_keyspaces() {
456: return $this->client->describe_keyspaces();
457: }
458: }
459:
460: