MDL-39038 nasty workaround for Oracle NULL concats
authorPetr Škoda <commits@skodak.org>
Mon, 8 Apr 2013 21:16:55 +0000 (23:16 +0200)
committerPetr Škoda <commits@skodak.org>
Mon, 8 Apr 2013 21:23:08 +0000 (23:23 +0200)
lib/dml/oci_native_moodle_database.php
lib/dml/oci_native_moodle_package.sql
lib/dml/tests/dml_test.php

index 012dc23..f5cce9d 100644 (file)
@@ -1540,43 +1540,57 @@ class oci_native_moodle_database extends moodle_database {
     }
 
     public function sql_concat() {
-        // NOTE: Oracle concat implementation isn't ANSI compliant when using NULLs (the result of
-        // any concatenation with NULL must return NULL) because of his inability to differentiate
-        // NULLs and empty strings. So this function will cause some tests to fail. Hopefully
-        // it's only a side case and it won't affect normal concatenation operations in Moodle.
         $arr = func_get_args();
-        if ($this->oci_package_installed()) {
-            foreach ($arr as $k => $v) {
-                if (strpos($v, "'") === 0) {
-                    continue;
-                }
-                $arr[$k] = "MOODLELIB.UNDO_DIRTY_HACK($v)";
-            }
-        }
-        $s = implode(' || ', $arr);
-        if ($s === '') {
+        if (empty($arr)) {
             return " ' ' ";
         }
-        return " MOODLELIB.DIRTY_HACK($s) ";
+        foreach ($arr as $k => $v) {
+            if ($v === "' '") {
+                $arr[$k] = "'*OCISP*'"; // New mega hack.
+            }
+        }
+        $s = $this->recursive_concat($arr);
+        return " MOODLELIB.UNDO_MEGA_HACK($s) ";
     }
 
-    public function sql_concat_join($separator="' '", $elements=array()) {
-        if ($this->oci_package_installed()) {
-            foreach ($elements as $k => $v) {
-                if (strpos($v, "'") === 0) {
-                    continue;
-                }
-                $elements[$k] = "MOODLELIB.UNDO_DIRTY_HACK($v)";
+    public function sql_concat_join($separator="' '", $elements = array()) {
+        if ($separator === "' '") {
+            $separator = "'*OCISP*'"; // New mega hack.
+        }
+        foreach ($elements as $k => $v) {
+            if ($v === "' '") {
+                $elements[$k] = "'*OCISP*'"; // New mega hack.
             }
         }
         for ($n = count($elements)-1; $n > 0 ; $n--) {
             array_splice($elements, $n, 0, $separator);
         }
-        $s = implode(' || ', $elements);
-        if ($s === '') {
+        if (empty($elements)) {
             return " ' ' ";
         }
-        return " MOODLELIB.DIRTY_HACK($s) ";
+        $s = $this->recursive_concat($elements);
+        return " MOODLELIB.UNDO_MEGA_HACK($s) ";
+    }
+
+    /**
+     * Mega hacky magic to work around crazy Oracle NULL concats.
+     * @param array $args
+     * @return string
+     */
+    protected function recursive_concat(array $args) {
+        $count = count($args);
+        if ($count == 1) {
+            $arg = reset($args);
+            return $arg;
+        }
+        if ($count == 2) {
+            $args[] = "' '";
+            // No return here intentionally.
+        }
+        $first = array_shift($args);
+        $second = array_shift($args);
+        $third = $this->recursive_concat($args);
+        return "MOODLELIB.TRICONCAT($first, $second, $third)";
     }
 
     /**
index fed15f1..3fbba17 100644 (file)
@@ -41,8 +41,9 @@ FUNCTION GET_HANDLE  (lock_name IN VARCHAR2) RETURN VARCHAR2;
 FUNCTION GET_LOCK    (lock_name IN VARCHAR2, lock_timeout IN INTEGER) RETURN INTEGER;
 FUNCTION RELEASE_LOCK(lock_name IN VARCHAR2) RETURN INTEGER;
 
-FUNCTION DIRTY_HACK(somestring IN VARCHAR2) RETURN VARCHAR2;
 FUNCTION UNDO_DIRTY_HACK(hackedstring IN VARCHAR2) RETURN VARCHAR2;
+FUNCTION UNDO_MEGA_HACK(hackedstring IN VARCHAR2) RETURN VARCHAR2;
+FUNCTION TRICONCAT(string1 IN VARCHAR2, string2 IN VARCHAR2, string3 IN VARCHAR2) RETURN VARCHAR2;
 
 END MOODLELIB;
 /
@@ -101,15 +102,6 @@ BEGIN
     RETURN 1;
 END RELEASE_LOCK;
 
-FUNCTION DIRTY_HACK(somestring IN VARCHAR2) RETURN VARCHAR2 IS
-
-BEGIN
-    IF somestring = '' THEN
-      RETURN ' ';
-    END IF;
-    RETURN somestring;
-END DIRTY_HACK;
-
 FUNCTION UNDO_DIRTY_HACK(hackedstring IN VARCHAR2) RETURN VARCHAR2 IS
 
 BEGIN
@@ -119,5 +111,36 @@ BEGIN
     RETURN hackedstring;
 END UNDO_DIRTY_HACK;
 
+FUNCTION UNDO_MEGA_HACK(hackedstring IN VARCHAR2) RETURN VARCHAR2 IS
+
+BEGIN
+    IF hackedstring IS NULL THEN
+        RETURN hackedstring;
+    END IF;
+    RETURN REPLACE(hackedstring, '*OCISP*', ' ');
+END UNDO_MEGA_HACK;
+
+FUNCTION TRICONCAT(string1 IN VARCHAR2, string2 IN VARCHAR2, string3 IN VARCHAR2) RETURN VARCHAR2 IS
+    stringresult VARCHAR2(1333);
+BEGIN
+    IF string1 IS NULL THEN
+        RETURN NULL;
+    END IF;
+    IF string2 IS NULL THEN
+        RETURN NULL;
+    END IF;
+    IF string3 IS NULL THEN
+        RETURN NULL;
+    END IF;
+
+    stringresult := CONCAT(CONCAT(MOODLELIB.UNDO_DIRTY_HACK(string1), MOODLELIB.UNDO_DIRTY_HACK(string2)), MOODLELIB.UNDO_DIRTY_HACK(string3));
+
+    IF stringresult IS NULL THEN
+        RETURN ' ';
+    END IF;
+
+    RETURN stringresult;
+END;
+
 END MOODLELIB;
 /
index 01dc05d..9660b79 100644 (file)
@@ -3796,7 +3796,7 @@ class dml_testcase extends database_driver_testcase {
         $this->assertEquals('123456', $DB->get_field_sql($sql, $params));
         // float, null and strings
         $params = array(123.45, null, 'test');
-        $this->assertNull($DB->get_field_sql($sql, $params), 'ANSI behaviour: Concatenating NULL must return NULL - But in Oracle :-(. [%s]'); // Concatenate NULL with anything result = NULL
+        $this->assertNull($DB->get_field_sql($sql, $params)); // Concatenate NULL with anything result = NULL
 
         // Testing fieldnames + values and also integer fieldnames
         $table = $this->get_test_table();