import std.stdio; import std.boxer; import std.string : str = toString, toupper, tolower; static char[] excProp (char[] propertyType, char[] propertyName, char[] defVal) { char[] keyName = "K_" ~ toupper (propertyName); char[] varName = tolower(propertyName[0..1]) ~ propertyName[1..$]; char[] setCode = "typeof(this) set"~propertyName~" ("~propertyType~" "~varName~") {\n" " m_propertyBag["~keyName~"] = box ("~varName~");\n" " return this;\n" "}"; char[] getCode = propertyType~" "~varName~" () {\n" " Box v = getProperty ("~keyName~");\n" " if (v.type !is null) {\n" " return unbox!("~propertyType~")(v);\n" " } else {\n" " return "~defVal~";\n" " }\n" "}"; char[] keyCode = "private static const final "~keyName~" = \""~propertyName~"\";"; return setCode ~ "\n\n" ~ getCode ~ "\n\n" ~ keyCode; } static char[] excPropC (char[] propertyName) { return excProp ("char[]", propertyName, "null"); } static char[] excPropI (char[] propertyName) { return excProp ("int", propertyName, "0"); } public class SqlException : Exception { this (char[] msg) { super (msg); } this (char[] msg, SqlException cause) { super (msg); m_cause = cause; } typeof(this) initCause (SqlException cause) { m_cause = cause; return this; } SqlException cause () { return m_cause; } /** * Set the SQL text that caused, or was related to the exception. * * This is not a write property method so as to facilitate method chaining. */ mixin (excPropC ("Sql")); typeof(this) setProperty(T) (char[] property, T value) { m_propertyBag [property] = box (value); return this; } Box getProperty (char[] property) { if (property in m_propertyBag) { return m_propertyBag [property]; } else { if (m_cause !is null) { return m_cause.getProperty (property); } else { return Box(); } } } Box[char[]] propertyBag () { return m_propertyBag; } char[] toString () { /* Format properties for output. The properties are listed one to a line * of format 'name : value'. */ char[] pstr; foreach (p; m_propertyBag.keys.sort) { pstr ~= "\n" ~ p ~ ": " ~ m_propertyBag[p].toString (); } /* Prepend the exception type to the message. */ char[] tmpMsg = this.classinfo.name ~ ": " ~ msg; /* If the exception has properties, append the properties to the exception * message separated by an empty line. */ if (pstr.length > 0) { tmpMsg ~= "\n" ~ pstr; } /* If the exception has a cause, append the cause descripton separated by * an empty line and after the words "caused by: ". */ if (m_cause !is null) { tmpMsg ~= "\n\ncaused by: " ~ m_cause.toString (); } return tmpMsg; } protected Box[char[]] m_propertyBag; protected SqlException m_cause; } private template ExceptionConstructor () { this (char[] msg) {super (msg);} this (char[] msg, SqlException cause) {super (msg, cause);} /** * Override initCause to return the type of the instantiated * exception rather than the base exception. This is only necessary so * developers can set the cause before the properties. */ override typeof(this) initCause (SqlException cause) { super.initCause (cause); return this; } } public class SqlDatabaseException : SqlException { mixin ExceptionConstructor; mixin (excPropC ("SqlState")); mixin (excPropI ("VendorCode")); mixin (excPropC ("VendorMsg")); } public class SqlDataException : SqlDatabaseException { mixin ExceptionConstructor; mixin (excPropC ("TypeExpected")); mixin (excPropC ("TypeSupplied")); } public class SqlProgrammingException : SqlDatabaseException { mixin ExceptionConstructor; } public class SqlBindException : SqlProgrammingException { mixin ExceptionConstructor; mixin (excPropC ("BindName")); mixin (excPropI ("BindPosition")); } public class DbException : SqlException { this (char[] msg) { super (msg); } } void main () { //~ try { //~ try { //~ // Within position bind method. The name of the bind parameter is not //~ // known, only the position. //~ throw (new SqlProgrammingException ("Unable to bind parameter value.")) //~ .setVendorMsg ("Data type mismatch") //~ .setVendorCode (20) //~ .setSqlState ("2200G") //~ .setSql ("SELECT * FROM TABLE WHERE A = :somevalue") //~ .setProperty ("BindPosition", 1) //~ ; //~ } catch (SqlException e) { //~ // Within name lookup method. The name lookup method finds the position //~ // of a parameter based on the name. It then calls the position //~ // bind method. //~ e.setProperty ("BindName", "somevalue"); //~ throw e; //~ } //~ } catch (SqlProgrammingException e) { //~ writefln ("%s: \n---\n%s\n----", typeid(typeof(e)), e); //~ } catch (SqlDatabaseException e) { //~ writefln ("%s: \n---\n%s\n----", typeid(typeof(e)), e); //~ } catch (Exception e) { //~ writefln ("%s: \n---\n%s\n----", typeid(typeof(e)), e); //~ } try { //~ SqlException e = //~ (new SqlDataException ("Data type mismatch")) //~ .setExpectedType ("int") //~ .setSuppliedType ("char[]") //~ .setSqlState ("2200G") //~ .setVendorMsg ("Data type mismatch") //~ .setSql ("SELECT * FROM TABLE WHERE A = :somevalue"); //~ throw (new SqlProgrammingException ("Unable to bind parameter value", e)) //~ .setProperty ("BindName", "somevalue") //~ .setProperty ("BindPosition", 1); //~ throw (new SqlProgrammingException ("Unable to bind parameter value")) //~ .initCause ( //~ (new SqlDataException ("Data type mismatch")) //~ .setTypeExpected ("int") //~ .setTypeSupplied ("char[]") //~ .setVendorMsg ("Data type mismatch") //~ .setSql ("SELECT * FROM TABLE WHERE A = :somevalue") //~ ) //~ .setSqlState ("2200G") //~ .setVendorCode (42) //~ .setProperty ("BindName", "somevalue") //~ .setProperty ("BindPosition", 1); throw (new SqlBindException ("Unable to bind parameter value")) .initCause ( (new SqlDataException ("Data type mismatch")) .setTypeExpected ("int") .setTypeSupplied ("char[]") .setVendorMsg ("Data type mismatch") .setVendorCode (42) .setSqlState ("2200G") .setSql ("SELECT * FROM TABLE WHERE A = :somevalue") ) .setBindName ("somevalue") .setBindPosition (1); } catch (SqlException e) { writefln ("%s", e); writefln ("\n---\nSQL: %s", e.sql); } }