4 using System
.Collections
;
5 using System
.Collections
.Generic
;
6 using System
.Security
.Cryptography
;
11 public enum VxCopyOpts
: int
19 Verbose
= ShowProgress
| ShowDiff
,
22 internal class VxSchemaElement
: IComparable
35 public virtual string text
{
41 public bool encrypted
{
42 get { return _encrypted; }
46 get { return type + "/" + name; }
49 public static VxSchemaElement
create(string type
, string name
,
50 string text
, bool encrypted
)
54 return new VxSchemaTable(name
, text
);
55 } catch (ArgumentException
) {
56 // if the table data is invalid, just ignore it.
57 // We'll fall through and load a VxSchemaElement instead.
60 return new VxSchemaElement(type
, name
, text
, encrypted
);
63 protected VxSchemaElement(string newtype
, string newname
,
64 string newtext
, bool newencrypted
)
68 _encrypted
= newencrypted
;
72 public static VxSchemaElement
create(VxSchemaElement copy
)
74 return create(copy
.type
, copy
.name
, copy
.text
, copy
.encrypted
);
77 public static VxSchemaElement
create(IEnumerable
<WvAutoCast
> _elem
)
79 var elem
= _elem
.GetEnumerator();
80 return create(elem
.pop(), elem
.pop(), elem
.pop(), elem
.pop() > 0);
83 public void Write(WvDbusWriter writer
)
88 byte encb
= (byte)(encrypted
? 1 : 0);
92 public string GetKey()
94 return VxSchema
.GetKey(type
, name
, encrypted
);
97 // It's not guaranteed that the text field will be valid SQL. Give
98 // subclasses a chance to translate.
99 public virtual string ToSql()
104 // Returns the element's text, along with a header line containing the MD5
105 // sum of the text, and the provided database checksums. This format is
106 // suitable for serializing to disk.
107 public string ToStringWithHeader(VxSchemaChecksum sum
)
109 byte[] md5
= MD5
.Create().ComputeHash(text
.ToUTF8());
111 return String
.Format("!!SCHEMAMATIC {0} {1} \r\n{2}",
112 md5
.ToHex().ToLower(), sum
.GetSumString(), text
);
115 public int CompareTo(object obj
)
117 if (!(obj
is VxSchemaElement
))
118 throw new ArgumentException("object is not a VxSchemaElement");
120 VxSchemaElement other
= (VxSchemaElement
)obj
;
121 return GetKey().CompareTo(other
.GetKey());
124 public static string GetDbusSignature()
130 // Represents an element of a table, such as a column or index.
131 // Each element has a type, such as "column", and a series of key,value
132 // parameters, such as ("name","MyColumn"), ("type","int"), etc.
133 internal class VxSchemaTableElement
135 public string elemtype
;
136 // We can't use a Dictionary<string,string> because we might have repeated
137 // keys (such as two columns for an index).
138 public List
<KeyValuePair
<string,string>> parameters
;
140 public VxSchemaTableElement(string type
)
143 parameters
= new List
<KeyValuePair
<string,string>>();
146 public void AddParam(string name
, string val
)
148 parameters
.Add(new KeyValuePair
<string,string>(name
, val
));
151 // Returns the first parameter found with the given name.
152 // Returns an empty string if none found.
153 public string GetParam(string name
)
155 foreach (var kvp
in parameters
)
161 // Returns a list of all parameters found with the given name.
162 // Returns an empty list if none found.
163 public List
<string> GetParamList(string name
)
165 List
<string> results
= new List
<string>();
166 foreach (var kvp
in parameters
)
168 results
.Add(kvp
.Value
);
172 public bool HasDefault()
174 return elemtype
== "column" && GetParam("default").ne();
177 // Serializes to "elemtype: key1=value1,key2=value2" format.
178 public override string ToString()
180 StringBuilder sb
= new StringBuilder();
182 sb
.Append(elemtype
+ ": ");
183 foreach (var param
in parameters
)
188 sb
.Append(param
.Key
+ "=" + param
.Value
);
192 return sb
.ToString();
195 // Returns a string uniquely identifying this element within the table.
196 // Generally includes the element's name, if it has one.
197 public string GetElemKey()
199 if (elemtype
== "primary-key")
202 return elemtype
+ ": " + GetParam("name");
206 internal class VxSchemaTable
: VxSchemaElement
,
207 IEnumerable
<VxSchemaTableElement
>
209 // A list of table elements, so we can maintain the original order
210 private List
<VxSchemaTableElement
> elems
;
211 // A dictionary of table elements, so we can quickly check if we have
213 private Dictionary
<string, VxSchemaTableElement
> elemdict
;
215 public VxSchemaTable(string newname
) :
216 base("Table", newname
, null, false)
218 elems
= new List
<VxSchemaTableElement
>();
219 elemdict
= new Dictionary
<string, VxSchemaTableElement
>();
222 public VxSchemaTable(string newname
, string newtext
) :
223 base("Table", newname
, null, false)
225 elems
= new List
<VxSchemaTableElement
>();
226 elemdict
= new Dictionary
<string, VxSchemaTableElement
>();
227 // Parse the new text
231 public VxSchemaTable(VxSchemaElement elem
) :
232 base("Table", elem
.name
, null, false)
234 elems
= new List
<VxSchemaTableElement
>();
235 elemdict
= new Dictionary
<string, VxSchemaTableElement
>();
236 // Parse the new text
240 public bool Contains(string elemkey
)
242 return elemdict
.ContainsKey(elemkey
);
245 public VxSchemaTableElement
this[string elemkey
]
249 return elemdict
[elemkey
];
253 // Implement the IEnumerator interface - just punt to the list
254 IEnumerator IEnumerable
.GetEnumerator()
256 return elems
.GetEnumerator();
259 public IEnumerator
<VxSchemaTableElement
> GetEnumerator()
261 return elems
.GetEnumerator();
264 public override string text
266 // Other schema elements just store their text verbatim.
267 // We parse it on input and recreate it on output in order to
268 // provide more sensible updating of tables in the database.
271 StringBuilder sb
= new StringBuilder();
272 foreach (var elem
in elems
)
273 sb
.Append(elem
.ToString() + "\n");
274 return sb
.ToString();
280 char[] equals
= {'='}
;
281 char[] comma
= {','}
;
282 foreach (string line
in value.Split('\n'))
285 if (line
.Length
== 0)
288 string typeseparator
= ": ";
289 int index
= line
.IndexOf(typeseparator
);
291 throw new ArgumentException
292 (wv
.fmt("Malformed line in {0}: {1}", key
, line
));
293 string type
= line
.Remove(index
);
294 string rest
= line
.Substring(index
+ typeseparator
.Length
);
296 var elem
= new VxSchemaTableElement(type
);
298 foreach (string kvstr
in rest
.Split(comma
))
300 string[] kv
= kvstr
.Split(equals
, 2);
302 throw new ArgumentException(wv
.fmt(
303 "Invalid entry '{0}' in line '{1}'",
307 new KeyValuePair
<string,string>(kv
[0], kv
[1]));
314 public string GetDefaultPKName()
316 return "PK_" + this.name
;
319 public string GetDefaultDefaultName(string colname
)
321 return wv
.fmt("{0}_{1}_default", this.name
, colname
);
324 // Include any default constraints by, er, default.
325 public string ColumnToSql(VxSchemaTableElement elem
)
327 return ColumnToSql(elem
, true);
330 public string ColumnToSql(VxSchemaTableElement elem
, bool include_default
)
332 string colname
= elem
.GetParam("name");
333 string typename
= elem
.GetParam("type");
334 string lenstr
= elem
.GetParam("length");
335 string defval
= elem
.GetParam("default");
336 string nullstr
= elem
.GetParam("null");
337 string prec
= elem
.GetParam("precision");
338 string scale
= elem
.GetParam("scale");
339 string ident_seed
= elem
.GetParam("identity_seed");
340 string ident_incr
= elem
.GetParam("identity_incr");
342 string identstr
= "";
343 if (ident_seed
.ne() && ident_incr
.ne())
344 identstr
= wv
.fmt(" IDENTITY ({0},{1})", ident_seed
, ident_incr
);
348 else if (nullstr
== "0")
349 nullstr
= " NOT NULL";
354 lenstr
= " (" + lenstr
+ ")";
355 else if (prec
.ne() && scale
.ne())
356 lenstr
= wv
.fmt(" ({0},{1})", prec
, scale
);
358 if (include_default
&& defval
.ne())
360 string defname
= GetDefaultDefaultName(colname
);
361 defval
= " CONSTRAINT " + defname
+ " DEFAULT " + defval
;
366 return wv
.fmt("[{0}] [{1}]{2}{3}{4}{5}",
367 colname
, typename
, lenstr
, defval
, nullstr
, identstr
);
370 public string IndexToSql(VxSchemaTableElement elem
)
372 List
<string> idxcols
= elem
.GetParamList("column");
373 string idxname
= elem
.GetParam("name");
374 string unique
= elem
.GetParam("unique");
375 string clustered
= elem
.GetParam("clustered") == "1" ?
378 if (unique
!= "" && unique
!= "0")
384 "CREATE {0}{1}INDEX [{2}] ON [{3}] \n\t({4});",
385 unique
, clustered
, idxname
, this.name
, idxcols
.join(", "));
388 public string PrimaryKeyToSql(VxSchemaTableElement elem
)
390 List
<string> idxcols
= elem
.GetParamList("column");
391 string idxname
= elem
.GetParam("name");
392 string clustered
= elem
.GetParam("clustered") == "1" ?
393 " CLUSTERED" : " NONCLUSTERED";
396 idxname
= GetDefaultPKName();
399 "ALTER TABLE [{0}] ADD CONSTRAINT [{1}] PRIMARY KEY{2}\n" +
401 this.name
, idxname
, clustered
, idxcols
.join(", "));
404 public override string ToSql()
406 List
<string> cols
= new List
<string>();
407 List
<string> indexes
= new List
<string>();
409 foreach (var elem
in elems
)
411 if (elem
.elemtype
== "column")
412 cols
.Add(ColumnToSql(elem
));
413 else if (elem
.elemtype
== "index")
414 indexes
.Add(IndexToSql(elem
));
415 else if (elem
.elemtype
== "primary-key")
419 throw new VxBadSchemaException(
420 "Multiple primary key statements are not " +
421 "permitted in table definitions.\n" +
422 "Conflicting statement: " + elem
.ToString() + "\n");
424 pkey
= PrimaryKeyToSql(elem
);
429 throw new VxBadSchemaException("No columns in schema.");
431 string table
= String
.Format("CREATE TABLE [{0}] (\n\t{1});\n\n{2}{3}\n",
432 name
, cols
.join(",\n\t"), pkey
, indexes
.join("\n"));
436 private void Add(VxSchemaTableElement elem
)
440 string elemkey
= elem
.GetElemKey();
442 if (elemdict
.ContainsKey(elemkey
))
443 throw new VxBadSchemaException(wv
.fmt("Duplicate table entry " +
444 "'{0}' found.", elemkey
));
446 elemdict
.Add(elemkey
, elem
);
449 public void AddColumn(string name
, string type
, int isnullable
,
450 string len
, string defval
, string prec
, string scale
,
451 int isident
, string ident_seed
, string ident_incr
)
453 var elem
= new VxSchemaTableElement("column");
454 // FIXME: Put the table name here or not? Might be handy, but could
455 // get out of sync with e.g. filename or whatnot.
456 elem
.AddParam("name", name
);
457 elem
.AddParam("type", type
);
458 elem
.AddParam("null", isnullable
.ToString());
460 elem
.AddParam("length", len
);
462 elem
.AddParam("default", defval
);
464 elem
.AddParam("precision", prec
);
466 elem
.AddParam("scale", scale
);
469 elem
.AddParam("identity_seed", ident_seed
);
470 elem
.AddParam("identity_incr", ident_incr
);
475 public void AddIndex(string name
, int unique
, int clustered
,
476 params string[] columns
)
478 WvLog log
= new WvLog("AddIndex", WvLog
.L
.Debug4
);
479 log
.print("Adding index on {0}, name={1}, unique={2}, clustered={3},\n",
480 columns
.join(","), name
, unique
, clustered
);
481 var elem
= new VxSchemaTableElement("index");
483 foreach (string col
in columns
)
484 elem
.AddParam("column", col
);
485 elem
.AddParam("name", name
);
486 elem
.AddParam("unique", unique
.ToString());
487 elem
.AddParam("clustered", clustered
.ToString());
492 public void AddPrimaryKey(string name
, int clustered
,
493 params string[] columns
)
495 WvLog log
= new WvLog("AddPrimaryKey", WvLog
.L
.Debug4
);
496 log
.print("Adding primary key '{0}' on {1}, clustered={2}\n",
497 name
, columns
.join(","), clustered
);
498 var elem
= new VxSchemaTableElement("primary-key");
500 if (name
.ne() && name
!= GetDefaultPKName())
501 elem
.AddParam("name", name
);
503 foreach (string col
in columns
)
504 elem
.AddParam("column", col
);
505 elem
.AddParam("clustered", clustered
.ToString());
510 // Figure out what changed between oldtable and newtable.
511 // Returns any deleted elements first, followed by any modified or added
512 // elements in the same order they occur in newtable. Any returned
513 // elements scheduled for changing are from the new table.
514 public static List
<KeyValuePair
<VxSchemaTableElement
, VxDiffType
>> GetDiff(
515 VxSchemaTable oldtable
, VxSchemaTable newtable
)
517 WvLog log
= new WvLog("SchemaTable GetDiff", WvLog
.L
.Debug4
);
518 var diff
= new List
<KeyValuePair
<VxSchemaTableElement
, VxDiffType
>>();
520 foreach (var elem
in oldtable
.elems
)
522 string elemkey
= elem
.GetElemKey();
523 if (!newtable
.Contains(elemkey
))
525 log
.print("Scheduling {0} for removal.\n", elemkey
);
526 diff
.Add(new KeyValuePair
<VxSchemaTableElement
, VxDiffType
>(
527 oldtable
[elemkey
], VxDiffType
.Remove
));
530 foreach (var elem
in newtable
.elems
)
532 string elemkey
= elem
.GetElemKey();
533 if (!oldtable
.Contains(elemkey
))
535 log
.print("Scheduling {0} for addition.\n", elemkey
);
536 diff
.Add(new KeyValuePair
<VxSchemaTableElement
, VxDiffType
>(
537 newtable
[elemkey
], VxDiffType
.Add
));
539 else if (elem
.ToString() != oldtable
[elemkey
].ToString())
541 log
.print("Scheduling {0} for change.\n", elemkey
);
542 diff
.Add(new KeyValuePair
<VxSchemaTableElement
, VxDiffType
>(
543 newtable
[elemkey
], VxDiffType
.Change
));
551 // The schema elements for a set of database elements
552 internal class VxSchema
: Dictionary
<string, VxSchemaElement
>
554 public static ISchemaBackend
create(string moniker
)
556 ISchemaBackend sm
= WvMoniker
<ISchemaBackend
>.create(moniker
);
557 if (sm
== null && Directory
.Exists(moniker
))
558 sm
= WvMoniker
<ISchemaBackend
>.create("dir:" + moniker
);
560 sm
= WvMoniker
<ISchemaBackend
>.create("dbi:" + moniker
);
563 (wv
.fmt("No moniker found for '{0}'", moniker
));
571 // Convenience method for making single-element schemas
572 public VxSchema(VxSchemaElement elem
)
577 public VxSchema(VxSchema copy
)
579 foreach (KeyValuePair
<string,VxSchemaElement
> p
in copy
)
580 this.Add(p
.Key
, VxSchemaElement
.create(p
.Value
));
583 public VxSchema(IEnumerable
<WvAutoCast
> sch
)
585 foreach (var row
in sch
)
587 VxSchemaElement elem
= VxSchemaElement
.create(row
);
588 Add(elem
.GetKey(), elem
);
592 public void WriteSchema(WvDbusWriter writer
)
594 writer
.WriteArray(8, this, (w2
, p
) => {
599 // Returns only the elements of the schema that are affected by the diff.
600 // If an element is scheduled to be removed, clear its text field.
601 // Produces a VxSchema that, if sent to a schema backend's Put, will
602 // update the schema as indicated by the diff.
603 public VxSchema
GetDiffElements(VxSchemaDiff diff
)
605 VxSchema diffschema
= new VxSchema();
606 foreach (KeyValuePair
<string,VxDiffType
> p
in diff
)
608 if (!this.ContainsKey(p
.Key
))
609 throw new ArgumentException("The provided diff does not " +
610 "match the schema: extra element '" +
611 (char)p
.Value
+ " " + p
.Key
+ "'");
612 if (p
.Value
== VxDiffType
.Remove
)
614 VxSchemaElement elem
= VxSchemaElement
.create(this[p
.Key
]);
616 diffschema
[p
.Key
] = elem
;
618 else if (p
.Value
== VxDiffType
.Add
|| p
.Value
== VxDiffType
.Change
)
620 diffschema
[p
.Key
] = VxSchemaElement
.create(this[p
.Key
]);
626 public void Add(string type
, string name
, string text
, bool encrypted
)
628 string key
= GetKey(type
, name
, encrypted
);
629 if (this.ContainsKey(key
))
630 this[key
].text
+= text
;
632 this.Add(key
, VxSchemaElement
.create(type
, name
, text
, encrypted
));
635 public static string GetKey(string type
, string name
, bool encrypted
)
637 string enc_str
= encrypted
? "-Encrypted" : "";
638 return String
.Format("{0}{1}/{2}", type
, enc_str
, name
);
641 // ParseKey used to live here, but moved to VxSchemaChecksums.
642 public static void ParseKey(string key
, out string type
, out string name
)
644 VxSchemaChecksums
.ParseKey(key
, out type
, out name
);
648 public static string GetDbusSignature()
650 return String
.Format("a({0})", VxSchemaElement
.GetDbusSignature());
653 // Make dest look like source. Only copies the bits that need updating.
654 // Note: this is a slightly funny spot to put this method; it really
655 // belongs in ISchemaBackend, but you can't put methods in interfaces.
656 public static VxSchemaErrors
CopySchema(ISchemaBackend source
,
659 return VxSchema
.CopySchema(source
, dest
, VxCopyOpts
.None
);
662 public static VxSchemaErrors
CopySchema(ISchemaBackend source
,
663 ISchemaBackend dest
, VxCopyOpts opts
)
665 WvLog log
= new WvLog("CopySchema");
667 if ((opts
& VxCopyOpts
.ShowProgress
) == 0)
668 log
= new WvLog("CopySchema", WvLog
.L
.Debug5
);
670 bool show_diff
= (opts
& VxCopyOpts
.ShowDiff
) != 0;
671 bool dry_run
= (opts
& VxCopyOpts
.DryRun
) != 0;
672 bool destructive
= (opts
& VxCopyOpts
.Destructive
) != 0;
674 log
.print("Retrieving schema checksums from source.\n");
675 VxSchemaChecksums srcsums
= source
.GetChecksums();
677 log
.print("Retrieving schema checksums from dest.\n");
678 VxSchemaChecksums destsums
= dest
.GetChecksums();
680 if (srcsums
.Count
== 0 && destsums
.Count
!= 0)
682 log
.print("Source index is empty! " +
683 "Refusing to delete entire database.\n");
684 return new VxSchemaErrors();
687 List
<string> names
= new List
<string>();
689 log
.print("Computing diff.\n");
690 VxSchemaDiff diff
= new VxSchemaDiff(destsums
, srcsums
);
694 log
.print("No changes.\n");
695 return new VxSchemaErrors();
700 log
.print("Changes to apply:\n");
701 log
.print(WvLog
.L
.Info
, diff
.ToString());
704 log
.print("Parsing diff.\n");
705 List
<string> to_drop
= new List
<string>();
706 foreach (KeyValuePair
<string,VxDiffType
> p
in diff
)
710 case VxDiffType
.Remove
:
714 case VxDiffType
.Change
:
720 log
.print("Retrieving updated schema.\n");
721 VxSchema to_put
= source
.Get(names
);
724 return new VxSchemaErrors();
726 VxSchemaErrors drop_errs
= new VxSchemaErrors();
727 VxSchemaErrors put_errs
= new VxSchemaErrors();
729 // We know at least one of to_drop and to_put must have something in
730 // it, otherwise the diff would have been empty.
732 if (to_drop
.Count
> 0)
734 log
.print("Dropping deleted elements.\n");
735 drop_errs
= dest
.DropSchema(to_drop
);
738 VxPutOpts putopts
= VxPutOpts
.None
;
740 putopts
|= VxPutOpts
.Destructive
;
743 log
.print("Updating and adding elements.\n");
744 put_errs
= dest
.Put(to_put
, srcsums
, putopts
);
747 // Combine the two sets of errors.
748 foreach (var kvp
in drop_errs
)
749 put_errs
.Add(kvp
.Key
, kvp
.Value
);