[ASTERIXDB-2667][FUN] Share code base between string functions

- user model changes: no
- storage format changes: no
- interface changes: no

Details:
- String functions concat, string_concat and string_join
  now share the same code instead of each having a different
  evaluator class.
- Added test cases for string_join function.
- Added support to deep missing/null checking in list
  items. Now it's possible to give a deeper (list items)
  missing value a higher priority over null arguments
  outside a list between parameters.
  Example:
  somefun([1, missing], null);
  It's possible to deeply check the list items first,
  and hence return a missing, or only do first level check
  and output would be null.

Change-Id: I41b644c6841b222d1c6c529b2f9189f42178e28c
Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/4023
Contrib: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Hussain Towaileb <hussainht@gmail.com>
Reviewed-by: Ali Alsuliman <ali.al.solaiman@gmail.com>
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat_01/concat_01.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/000/concat.1.ddl.sqlpp
similarity index 100%
rename from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat_01/concat_01.1.ddl.sqlpp
rename to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/000/concat.1.ddl.sqlpp
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat_01/concat_01.2.update.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/000/concat.2.update.sqlpp
similarity index 100%
rename from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat_01/concat_01.2.update.sqlpp
rename to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/000/concat.2.update.sqlpp
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat_01/concat_01.3.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/000/concat.3.query.sqlpp
similarity index 100%
rename from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat_01/concat_01.3.query.sqlpp
rename to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/000/concat.3.query.sqlpp
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat_02/concat_02.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/001/concat.1.ddl.sqlpp
similarity index 100%
rename from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat_02/concat_02.1.ddl.sqlpp
rename to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/001/concat.1.ddl.sqlpp
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat_02/concat_02.2.update.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/001/concat.2.update.sqlpp
similarity index 100%
rename from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat_02/concat_02.2.update.sqlpp
rename to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/001/concat.2.update.sqlpp
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat_02/concat_02.3.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/001/concat.3.query.sqlpp
similarity index 100%
rename from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat_02/concat_02.3.query.sqlpp
rename to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/001/concat.3.query.sqlpp
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat_03/concat_03.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/002/concat.1.ddl.sqlpp
similarity index 100%
rename from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat_03/concat_03.1.ddl.sqlpp
rename to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/002/concat.1.ddl.sqlpp
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat_03/concat_03.2.update.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/002/concat.2.update.sqlpp
similarity index 100%
rename from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat_03/concat_03.2.update.sqlpp
rename to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/002/concat.2.update.sqlpp
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat_03/concat_03.3.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/002/concat.3.query.sqlpp
similarity index 100%
rename from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat_03/concat_03.3.query.sqlpp
rename to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/002/concat.3.query.sqlpp
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-concat1/string-concat1.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/003/concat.0.query.sqlpp
similarity index 80%
copy from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-concat1/string-concat1.1.ddl.sqlpp
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/003/concat.0.query.sqlpp
index 21479a2..3c15a55 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-concat1/string-concat1.1.ddl.sqlpp
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/003/concat.0.query.sqlpp
@@ -17,6 +17,12 @@
  * under the License.
  */
 
-drop  dataverse test if exists;
-create  dataverse test;
-
+-- param max-warnings:json=1000
+[
+concat(),
+concat("1", "2", "3"),
+concat(1, 2, missing) is missing,
+concat(1, null, missing) is missing,
+concat(1, 2, 3) is null,
+concat(1, 2, null) is null
+];
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat_01/concat_01.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/004/concat.1.ddl.sqlpp
similarity index 100%
copy from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat_01/concat_01.1.ddl.sqlpp
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/004/concat.1.ddl.sqlpp
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat_01/concat_01.2.update.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/004/concat.2.update.sqlpp
similarity index 100%
copy from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat_01/concat_01.2.update.sqlpp
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/004/concat.2.update.sqlpp
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-concat1/string-concat1.3.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/004/concat.3.query.sqlpp
similarity index 100%
rename from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-concat1/string-concat1.3.query.sqlpp
rename to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/004/concat.3.query.sqlpp
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-concat2/string-concat2.1.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/005/concat.1.query.sqlpp
similarity index 100%
rename from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-concat2/string-concat2.1.query.sqlpp
rename to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/005/concat.1.query.sqlpp
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-concat2/string-concat2.2.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/005/concat.2.query.sqlpp
similarity index 100%
rename from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-concat2/string-concat2.2.query.sqlpp
rename to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/005/concat.2.query.sqlpp
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/strconcat01/strconcat01.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/006/concat.1.ddl.sqlpp
similarity index 100%
rename from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/strconcat01/strconcat01.1.ddl.sqlpp
rename to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/006/concat.1.ddl.sqlpp
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/strconcat01/strconcat01.2.update.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/006/concat.2.update.sqlpp
similarity index 100%
rename from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/strconcat01/strconcat01.2.update.sqlpp
rename to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/006/concat.2.update.sqlpp
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/strconcat01/strconcat01.3.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/006/concat.3.query.sqlpp
similarity index 100%
rename from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/strconcat01/strconcat01.3.query.sqlpp
rename to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/006/concat.3.query.sqlpp
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/strconcat02/strconcat02.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/007/concat.1.ddl.sqlpp
similarity index 100%
rename from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/strconcat02/strconcat02.1.ddl.sqlpp
rename to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/007/concat.1.ddl.sqlpp
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/strconcat02/strconcat02.2.update.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/007/concat.2.update.sqlpp
similarity index 100%
rename from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/strconcat02/strconcat02.2.update.sqlpp
rename to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/007/concat.2.update.sqlpp
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/strconcat02/strconcat02.3.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/007/concat.3.query.sqlpp
similarity index 100%
rename from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/strconcat02/strconcat02.3.query.sqlpp
rename to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/007/concat.3.query.sqlpp
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat_func/concat_func.1.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/008/concat.1.query.sqlpp
similarity index 100%
rename from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat_func/concat_func.1.query.sqlpp
rename to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/008/concat.1.query.sqlpp
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat_pipe/concat_pipe.1.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/concat_pipe/concat_pipe.1.query.sqlpp
similarity index 100%
rename from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat_pipe/concat_pipe.1.query.sqlpp
rename to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/concat_pipe/concat_pipe.1.query.sqlpp
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat_pipe_multi/concat_pipe_multi.1.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/concat_pipe_multi/concat_pipe_multi.1.query.sqlpp
similarity index 100%
rename from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat_pipe_multi/concat_pipe_multi.1.query.sqlpp
rename to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat/concat_pipe_multi/concat_pipe_multi.1.query.sqlpp
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-concat1/string-concat1.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/000/join.000.ddl.sqlpp
similarity index 77%
copy from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-concat1/string-concat1.1.ddl.sqlpp
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/000/join.000.ddl.sqlpp
index 21479a2..d5c4128 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-concat1/string-concat1.1.ddl.sqlpp
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/000/join.000.ddl.sqlpp
@@ -17,6 +17,14 @@
  * under the License.
  */
 
-drop  dataverse test if exists;
-create  dataverse test;
+drop dataverse test if exists;
+create dataverse test;
+use test;
+
+drop type test if exists;
+create type test as open { id: uuid, f0: int64 };
+
+drop dataset test if exists;
+
+create dataset test(test) primary key id autogenerated;
 
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-concat1/string-concat1.2.update.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/000/join.001.update.sqlpp
similarity index 60%
copy from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-concat1/string-concat1.2.update.sqlpp
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/000/join.001.update.sqlpp
index bd244d0..b72db45 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-concat1/string-concat1.2.update.sqlpp
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/000/join.001.update.sqlpp
@@ -17,3 +17,12 @@
  * under the License.
  */
 
+use test;
+insert into test({"f0": 1, "f1": ["1", "2", "3"], "f2": "-"});
+insert into test({"f0": 2, "f1": ["1", "2", "3", "4"], "f2": "-"});
+insert into test({"f0": 3, "f1": ["1", "2", "3", "4"], "f2": "|-|"});
+insert into test({"f0": 4, "f1": ["single"], "f2": "-"});
+insert into test({"f0": 5, "f1": {{"1", "2", "3"}}, "f2": "-"});
+insert into test({"f0": 6, "f1": {{"1", "2", "3", "4"}}, "f2": "-"});
+insert into test({"f0": 7, "f1": {{"1", "2", "3", "4"}}, "f2": "|-|"});
+insert into test({"f0": 8, "f1": {{"single"}}, "f2": "-"});
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-concat1/string-concat1.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/000/join.002.query.sqlpp
similarity index 60%
copy from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-concat1/string-concat1.1.ddl.sqlpp
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/000/join.002.query.sqlpp
index 21479a2..2163842 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-concat1/string-concat1.1.ddl.sqlpp
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/000/join.002.query.sqlpp
@@ -17,6 +17,15 @@
  * under the License.
  */
 
-drop  dataverse test if exists;
-create  dataverse test;
+use test;
 
+select value [
+(select value string_join(f1, f2) from test where f0 = 1)[0],
+(select value string_join(f1, f2) from test where f0 = 2)[0],
+(select value string_join(f1, f2) from test where f0 = 3)[0],
+(select value string_join(f1, f2) from test where f0 = 4)[0],
+(select value string_join(f1, f2) from test where f0 = 5)[0],
+(select value string_join(f1, f2) from test where f0 = 6)[0],
+(select value string_join(f1, f2) from test where f0 = 7)[0],
+(select value string_join(f1, f2) from test where f0 = 8)[0]
+];
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-concat1/string-concat1.2.update.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/000/join.003.ddl.sqlpp
similarity index 95%
rename from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-concat1/string-concat1.2.update.sqlpp
rename to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/000/join.003.ddl.sqlpp
index bd244d0..5c1e653 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-concat1/string-concat1.2.update.sqlpp
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/000/join.003.ddl.sqlpp
@@ -17,3 +17,5 @@
  * under the License.
  */
 
+drop dataverse test if exists;
+
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-concat1/string-concat1.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/001/join.000.ddl.sqlpp
similarity index 77%
copy from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-concat1/string-concat1.1.ddl.sqlpp
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/001/join.000.ddl.sqlpp
index 21479a2..d5c4128 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-concat1/string-concat1.1.ddl.sqlpp
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/001/join.000.ddl.sqlpp
@@ -17,6 +17,14 @@
  * under the License.
  */
 
-drop  dataverse test if exists;
-create  dataverse test;
+drop dataverse test if exists;
+create dataverse test;
+use test;
+
+drop type test if exists;
+create type test as open { id: uuid, f0: int64 };
+
+drop dataset test if exists;
+
+create dataset test(test) primary key id autogenerated;
 
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/001/join.001.update.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/001/join.001.update.sqlpp
new file mode 100644
index 0000000..23f9c2b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/001/join.001.update.sqlpp
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+use test;
+insert into test([{ "f0": 1, "f1": ["1"], "f2": 1}]);
+insert into test([{ "f0": 2, "f1": ["1"], "f2": missing}]);
+insert into test([{ "f0": 3, "f1": ["1", "2"], "f2": null}]);
+insert into test([{ "f0": 4, "f1": ["1", 1], "f2": "-"}]);
+insert into test([{ "f0": 5, "f1": ["1", missing], "f2": "-"}]);
+insert into test([{ "f0": 6, "f1": ["1", missing, null], "f2": "-"}]);
+insert into test([{ "f0": 7, "f1": ["1", null], "f2": "-"}]);
+insert into test([{ "f0": 8, "f1": {{"1"}}, "f2": 1}]);
+insert into test([{ "f0": 9, "f1": {{"1"}}, "f2": missing}]);
+insert into test([{ "f0": 10, "f1": {{"1", "2"}}, "f2": null}]);
+insert into test([{ "f0": 11, "f1": {{"1", 1}}, "f2": "-"}]);
+insert into test([{ "f0": 12, "f1": {{"1", missing}}, "f2": "-"}]);
+insert into test([{ "f0": 13, "f1": {{"1", missing, null}}, "f2": "-"}]);
+insert into test([{ "f0": 14, "f1": {{"1", null}}, "f2": "-"}]);
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-concat1/string-concat1.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/001/join.002.query.sqlpp
similarity index 84%
rename from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-concat1/string-concat1.1.ddl.sqlpp
rename to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/001/join.002.query.sqlpp
index 21479a2..553f32f 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-concat1/string-concat1.1.ddl.sqlpp
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/001/join.002.query.sqlpp
@@ -17,6 +17,10 @@
  * under the License.
  */
 
-drop  dataverse test if exists;
-create  dataverse test;
+// param max-warnings:json=1000
 
+use test;
+
+select value [string_join(f1, f2) is missing, string_join(f1, f2) is null]
+from test
+order by f0 asc;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-concat1/string-concat1.2.update.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/001/join.003.ddl.sqlpp
similarity index 95%
copy from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-concat1/string-concat1.2.update.sqlpp
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/001/join.003.ddl.sqlpp
index bd244d0..5c1e653 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-concat1/string-concat1.2.update.sqlpp
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/001/join.003.ddl.sqlpp
@@ -17,3 +17,5 @@
  * under the License.
  */
 
+drop dataverse test if exists;
+
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-concat1/string-concat1.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/002/join.000.query.sqlpp
similarity index 72%
copy from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-concat1/string-concat1.1.ddl.sqlpp
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/002/join.000.query.sqlpp
index 21479a2..d3f00a3 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-concat1/string-concat1.1.ddl.sqlpp
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/002/join.000.query.sqlpp
@@ -17,6 +17,13 @@
  * under the License.
  */
 
-drop  dataverse test if exists;
-create  dataverse test;
-
+[
+string_join(["1", "2", "3"], "-"),
+string_join(["1", "2", "3", "4"], "-"),
+string_join(["1", "2", "3", "4"], "|-|"),
+string_join(["single"], "-"),
+string_join({{"1", "2", "3"}}, "-"),
+string_join({{"1", "2", "3", "4"}}, "-"),
+string_join({{"1", "2", "3", "4"}}, "|-|"),
+string_join({{"single"}}, "-")
+];
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/003/join.000.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/003/join.000.query.sqlpp
new file mode 100644
index 0000000..002cc78
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/003/join.000.query.sqlpp
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// param max-warnings:json=1000
+
+[
+string_join(["1"], 1) is null,
+string_join(["1"], missing) is missing,
+string_join(["1", "2"], null) is null,
+string_join(["1", 1], "-") is null,
+string_join(["1", missing], "-") is missing,
+string_join(["1", missing, null], "-") is missing,
+string_join(["1", null], "-") is null,
+string_join({{"1"}}, 1) is null,
+string_join({{"1"}}, missing) is missing,
+string_join({{"1", "2"}}, null) is null,
+string_join({{"1", 1}}, "-") is null,
+string_join({{"1", missing}}, "-") is missing,
+string_join({{"1", missing, null}}, "-") is missing,
+string_join({{"1", null}}, "-") is null
+];
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-concat1/string-concat1.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/004/join.000.query.sqlpp
similarity index 73%
copy from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-concat1/string-concat1.1.ddl.sqlpp
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/004/join.000.query.sqlpp
index 21479a2..2a70645 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-concat1/string-concat1.1.ddl.sqlpp
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/004/join.000.query.sqlpp
@@ -17,6 +17,14 @@
  * under the License.
  */
 
-drop  dataverse test if exists;
-create  dataverse test;
+// param max-warnings:json=1000
 
+[
+string_join([], "-"),
+string_join(["1"], "-"),
+string_join(["1", "2"], "-"),
+string_join(["1", "2"], 1) is null,
+string_join(["1", "2"], missing) is missing,
+string_join(["1", missing], null) is null,
+string_join(["1", missing], 1) is missing
+];
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat_01/concat_01.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/005/join.1.ddl.sqlpp
similarity index 100%
copy from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat_01/concat_01.1.ddl.sqlpp
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/005/join.1.ddl.sqlpp
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat_01/concat_01.2.update.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/005/join.2.update.sqlpp
similarity index 100%
copy from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/concat_01/concat_01.2.update.sqlpp
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/005/join.2.update.sqlpp
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-join1/string-join1.3.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/005/join.3.query.sqlpp
similarity index 100%
rename from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-join1/string-join1.3.query.sqlpp
rename to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/join/005/join.3.query.sqlpp
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-join1/string-join1.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-join1/string-join1.1.ddl.sqlpp
deleted file mode 100644
index 21479a2..0000000
--- a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-join1/string-join1.1.ddl.sqlpp
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-drop  dataverse test if exists;
-create  dataverse test;
-
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-join1/string-join1.2.update.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-join1/string-join1.2.update.sqlpp
deleted file mode 100644
index bd244d0..0000000
--- a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-join1/string-join1.2.update.sqlpp
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/string/concat_01/concat_01.1.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/string/concat/000/concat.1.adm
similarity index 100%
rename from asterixdb/asterix-app/src/test/resources/runtimets/results/string/concat_01/concat_01.1.adm
rename to asterixdb/asterix-app/src/test/resources/runtimets/results/string/concat/000/concat.1.adm
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/string/concat_02/concat_02.1.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/string/concat/001/concat.1.adm
similarity index 100%
rename from asterixdb/asterix-app/src/test/resources/runtimets/results/string/concat_02/concat_02.1.adm
rename to asterixdb/asterix-app/src/test/resources/runtimets/results/string/concat/001/concat.1.adm
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/string/concat_03/concat_03.1.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/string/concat/002/concat.1.adm
similarity index 100%
rename from asterixdb/asterix-app/src/test/resources/runtimets/results/string/concat_03/concat_03.1.adm
rename to asterixdb/asterix-app/src/test/resources/runtimets/results/string/concat/002/concat.1.adm
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/string/concat/003/concat.1.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/string/concat/003/concat.1.adm
new file mode 100644
index 0000000..7390a31
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/string/concat/003/concat.1.adm
@@ -0,0 +1 @@
+[ "", "123", true, true, true, true ]
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/string/string-concat1/string-concat1.1.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/string/concat/004/concat.1.adm
similarity index 100%
rename from asterixdb/asterix-app/src/test/resources/runtimets/results/string/string-concat1/string-concat1.1.adm
rename to asterixdb/asterix-app/src/test/resources/runtimets/results/string/concat/004/concat.1.adm
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/string/string-concat2/string-concat2.1.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/string/concat/005/concat.1.adm
similarity index 100%
rename from asterixdb/asterix-app/src/test/resources/runtimets/results/string/string-concat2/string-concat2.1.adm
rename to asterixdb/asterix-app/src/test/resources/runtimets/results/string/concat/005/concat.1.adm
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/string/string-concat2/string-concat2.2.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/string/concat/005/concat.2.adm
similarity index 100%
rename from asterixdb/asterix-app/src/test/resources/runtimets/results/string/string-concat2/string-concat2.2.adm
rename to asterixdb/asterix-app/src/test/resources/runtimets/results/string/concat/005/concat.2.adm
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/string/strconcat01/strconcat01.1.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/string/concat/006/concat.1.adm
similarity index 100%
rename from asterixdb/asterix-app/src/test/resources/runtimets/results/string/strconcat01/strconcat01.1.adm
rename to asterixdb/asterix-app/src/test/resources/runtimets/results/string/concat/006/concat.1.adm
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/string/strconcat02/strconcat02.1.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/string/concat/007/concat.1.adm
similarity index 100%
rename from asterixdb/asterix-app/src/test/resources/runtimets/results/string/strconcat02/strconcat02.1.adm
rename to asterixdb/asterix-app/src/test/resources/runtimets/results/string/concat/007/concat.1.adm
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/string/concat_pipe_multi/concat_pipe_multi.1.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/string/concat/concat_pipe_multi/concat_pipe_multi.1.adm
similarity index 100%
rename from asterixdb/asterix-app/src/test/resources/runtimets/results/string/concat_pipe_multi/concat_pipe_multi.1.adm
rename to asterixdb/asterix-app/src/test/resources/runtimets/results/string/concat/concat_pipe_multi/concat_pipe_multi.1.adm
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/string/join/000/join.002.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/string/join/000/join.002.adm
new file mode 100644
index 0000000..2cfc16c
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/string/join/000/join.002.adm
@@ -0,0 +1 @@
+[ "1-2-3", "1-2-3-4", "1|-|2|-|3|-|4", "single", "1-2-3", "1-2-3-4", "1|-|2|-|3|-|4", "single" ]
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/string/join/001/join.002.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/string/join/001/join.002.adm
new file mode 100644
index 0000000..5a53a7d
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/string/join/001/join.002.adm
@@ -0,0 +1,14 @@
+[ false, true ]
+[ true, null ]
+[ false, true ]
+[ false, true ]
+[ true, null ]
+[ true, null ]
+[ false, true ]
+[ false, true ]
+[ true, null ]
+[ false, true ]
+[ false, true ]
+[ true, null ]
+[ true, null ]
+[ false, true ]
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/string/join/002/join.000.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/string/join/002/join.000.adm
new file mode 100644
index 0000000..2cfc16c
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/string/join/002/join.000.adm
@@ -0,0 +1 @@
+[ "1-2-3", "1-2-3-4", "1|-|2|-|3|-|4", "single", "1-2-3", "1-2-3-4", "1|-|2|-|3|-|4", "single" ]
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/string/join/003/join.000.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/string/join/003/join.000.adm
new file mode 100644
index 0000000..2ffdc89
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/string/join/003/join.000.adm
@@ -0,0 +1 @@
+[ true, true, true, true, true, true, true, true, true, true, true, true, true, true ]
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/string/join/004/join.000.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/string/join/004/join.000.adm
new file mode 100644
index 0000000..b8f461a
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/string/join/004/join.000.adm
@@ -0,0 +1 @@
+[ "", "1", "1-2", true, true, true, true ]
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/string/string-join1/string-join1.1.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/string/join/005/join.1.adm
similarity index 100%
rename from asterixdb/asterix-app/src/test/resources/runtimets/results/string/string-join1/string-join1.1.adm
rename to asterixdb/asterix-app/src/test/resources/runtimets/results/string/join/005/join.1.adm
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml b/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
index 201ec6b..fd19cd5 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
@@ -8921,33 +8921,54 @@
       </compilation-unit>
     </test-case>
     <test-case FilePath="string">
-      <compilation-unit name="concat_01">
-        <output-dir compare="Text">concat_01</output-dir>
+      <compilation-unit name="concat/001">
+        <output-dir compare="Text">concat/001</output-dir>
       </compilation-unit>
     </test-case>
     <test-case FilePath="string">
-      <compilation-unit name="concat_02">
-        <output-dir compare="Text">concat_02</output-dir>
+      <compilation-unit name="concat/002">
+        <output-dir compare="Text">concat/002</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="string" check-warnings="true">
+      <compilation-unit name="concat/003">
+        <output-dir compare="Text">concat/003</output-dir>
+        <expected-warn>Type mismatch: function string-concat expects its 1st input parameter to be of type string, but the actual input type is bigint (in line 26, at column 1)</expected-warn>
       </compilation-unit>
     </test-case>
     <test-case FilePath="string">
-      <compilation-unit name="concat_03">
-        <output-dir compare="Text">concat_03</output-dir>
+      <compilation-unit name="concat/004">
+        <output-dir compare="Text">concat/004</output-dir>
       </compilation-unit>
     </test-case>
     <test-case FilePath="string">
-      <compilation-unit name="concat_pipe">
-        <output-dir compare="Text">concat_03</output-dir>
+      <compilation-unit name="concat/005">
+        <output-dir compare="Text">concat/005</output-dir>
       </compilation-unit>
     </test-case>
     <test-case FilePath="string">
-      <compilation-unit name="concat_pipe_multi">
-        <output-dir compare="Text">concat_pipe_multi</output-dir>
+      <compilation-unit name="concat/006">
+        <output-dir compare="Text">concat/006</output-dir>
       </compilation-unit>
     </test-case>
     <test-case FilePath="string">
-      <compilation-unit name="concat_func">
-        <output-dir compare="Text">concat_03</output-dir>
+      <compilation-unit name="concat/007">
+        <output-dir compare="Text">concat/007</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="string">
+      <compilation-unit name="concat/008">
+        <output-dir compare="Text">concat/002</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="string">
+      <compilation-unit name="concat/concat_pipe">
+        <output-dir compare="Text">concat/002</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="string">
+      <compilation-unit name="concat/concat_pipe_multi">
+        <output-dir compare="Text">concat/concat_pipe_multi</output-dir>
       </compilation-unit>
     </test-case>
     <test-case FilePath="string">
@@ -9016,6 +9037,43 @@
       </compilation-unit>
     </test-case>
     <test-case FilePath="string">
+      <compilation-unit name="join/000">
+        <output-dir compare="Text">join/000</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="string" check-warnings="true">
+      <compilation-unit name="join/001">
+        <output-dir compare="Text">join/001</output-dir>
+        <expected-warn>Type mismatch: function string-join expects its 2nd input parameter to be of type string, but the actual input type is bigint (in line 24, at column 15)</expected-warn>
+        <expected-warn>Type mismatch: function string-join expects its 1st input parameter to be of type array, but the actual input type is bigint (in line 24, at column 15)</expected-warn>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="string">
+      <compilation-unit name="join/002">
+        <output-dir compare="Text">join/002</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="string" check-warnings="true">
+      <compilation-unit name="join/003">
+        <output-dir compare="Text">join/003</output-dir>
+        <expected-warn>Type mismatch: function string-join expects its 2nd input parameter to be of type string, but the actual input type is bigint (in line 23, at column 1)</expected-warn>
+        <expected-warn>Type mismatch: function string-join expects its 2nd input parameter to be of type string, but the actual input type is bigint (in line 30, at column 1)</expected-warn>
+        <expected-warn>Type mismatch: function string-join expects its 1st input parameter to be of type array, but the actual input type is bigint (in line 33, at column 1)</expected-warn>
+        <expected-warn>Type mismatch: function string-join expects its 1st input parameter to be of type array, but the actual input type is bigint (in line 26, at column 1)</expected-warn>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="string" check-warnings="true">
+      <compilation-unit name="join/004">
+        <output-dir compare="Text">join/004</output-dir>
+        <expected-warn>Type mismatch: function string-join expects its 2nd input parameter to be of type string, but the actual input type is bigint (in line 26, at column 1)</expected-warn>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="string" check-warnings="true">
+      <compilation-unit name="join/005">
+        <output-dir compare="Text">join/005</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="string">
       <compilation-unit name="length_01">
         <output-dir compare="Text">length_01</output-dir>
       </compilation-unit>
@@ -9448,26 +9506,6 @@
       </compilation-unit>
     </test-case>
     <test-case FilePath="string">
-      <compilation-unit name="strconcat01">
-        <output-dir compare="Text">strconcat01</output-dir>
-      </compilation-unit>
-    </test-case>
-    <test-case FilePath="string">
-      <compilation-unit name="strconcat02">
-        <output-dir compare="Text">strconcat02</output-dir>
-      </compilation-unit>
-    </test-case>
-    <test-case FilePath="string">
-      <compilation-unit name="string-concat1">
-        <output-dir compare="Text">string-concat1</output-dir>
-      </compilation-unit>
-    </test-case>
-    <test-case FilePath="string">
-      <compilation-unit name="string-concat2">
-        <output-dir compare="Text">string-concat2</output-dir>
-      </compilation-unit>
-    </test-case>
-    <test-case FilePath="string">
       <compilation-unit name="string-equal1">
         <output-dir compare="Text">string-equal1</output-dir>
       </compilation-unit>
@@ -9488,11 +9526,6 @@
       </compilation-unit>
     </test-case>
     <test-case FilePath="string">
-      <compilation-unit name="string-join1">
-        <output-dir compare="Text">string-join1</output-dir>
-      </compilation-unit>
-    </test-case>
-    <test-case FilePath="string">
       <compilation-unit name="string-to-codepoint">
         <output-dir compare="Text">string-to-codepoint</output-dir>
       </compilation-unit>
@@ -13649,22 +13682,22 @@
     <test-case FilePath="fun_return_null/string_fun" check-warnings="true">
       <compilation-unit name="string_fun_004">
         <output-dir compare="Text">string_fun_004</output-dir>
-        <expected-warn>Unsupported type: string-concat cannot process input type tinyint (in line 30, at column 1)</expected-warn>
+        <expected-warn>Type mismatch: function string-concat expects its 2nd input parameter to be of type string, but the actual input type is tinyint (in line 30, at column 1)</expected-warn>
         <expected-warn>Type mismatch: function substring expects its 2nd input parameter to be of type tinyint, smallint, integer, bigint, float or double, but the actual input type is string (in line 32, at column 1)</expected-warn>
         <expected-warn>Type mismatch: function substring expects its 1st input parameter to be of type string, but the actual input type is bigint (in line 33, at column 1)</expected-warn>
-        <expected-warn>Unsupported type: string-concat cannot process input type bigint (in line 31, at column 7)</expected-warn>
+        <expected-warn>Type mismatch: function string-concat expects its 2nd input parameter to be of type string, but the actual input type is bigint (in line 31, at column 7)</expected-warn>
         <expected-warn>Type mismatch: function codepoint-to-string expects its 1st input parameter to be of type array, but the actual input type is string (in line 34, at column 1)</expected-warn>
         <expected-warn>Unsupported type: codepoint-to-string cannot process input type string (in line 35, at column 1)</expected-warn>
 
-        <expected-warn>Unsupported type: string-concat cannot process input type tinyint (in line 30, at column 1)</expected-warn>
+        <expected-warn>Type mismatch: function string-concat expects its 2nd input parameter to be of type string, but the actual input type is tinyint (in line 30, at column 1)</expected-warn>
         <expected-warn>Type mismatch: function substring expects its 2nd input parameter to be of type tinyint, smallint, integer, bigint, float or double, but the actual input type is string (in line 32, at column 1)</expected-warn>
         <expected-warn>Type mismatch: function substring expects its 1st input parameter to be of type string, but the actual input type is bigint (in line 33, at column 1)</expected-warn>
-        <expected-warn>Unsupported type: string-concat cannot process input type bigint (in line 31, at column 7)</expected-warn>
+        <expected-warn>Type mismatch: function string-concat expects its 2nd input parameter to be of type string, but the actual input type is bigint (in line 31, at column 7)</expected-warn>
         <expected-warn>Type mismatch: function codepoint-to-string expects its 1st input parameter to be of type array, but the actual input type is string (in line 34, at column 1)</expected-warn>
         <expected-warn>Unsupported type: codepoint-to-string cannot process input type string (in line 35, at column 1)</expected-warn>
 
-        <expected-warn>Unsupported type: string-concat cannot process input type bigint (in line 30, at column 1)</expected-warn>
-        <expected-warn>Unsupported type: string-concat cannot process input type tinyint (in line 31, at column 7)</expected-warn>
+        <expected-warn>Type mismatch: function string-concat expects its 2nd input parameter to be of type string, but the actual input type is bigint (in line 30, at column 1)</expected-warn>
+        <expected-warn>Type mismatch: function string-concat expects its 2nd input parameter to be of type string, but the actual input type is tinyint (in line 31, at column 7)</expected-warn>
         <expected-warn>Type mismatch: function substring expects its 1st input parameter to be of type string, but the actual input type is bigint (in line 32, at column 1)</expected-warn>
         <expected-warn>Type mismatch: function codepoint-to-string expects its 1st input parameter to be of type array, but the actual input type is string (in line 33, at column 1)</expected-warn>
         <expected-warn>Unsupported type: codepoint-to-string cannot process input type string (in line 34, at column 1)</expected-warn>
diff --git a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/typecomputer/impl/StringJoinTypeComputer.java b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/typecomputer/impl/StringJoinTypeComputer.java
index 407a991..1777fa8 100644
--- a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/typecomputer/impl/StringJoinTypeComputer.java
+++ b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/typecomputer/impl/StringJoinTypeComputer.java
@@ -40,7 +40,7 @@
 
     @Override
     protected IAType getResultType(ILogicalExpression expr, IAType... strippedInputTypes) throws AlgebricksException {
-        return validArgs(strippedInputTypes) ? ASTRING : AUnionType.createNullableType(ASTRING);
+        return validArgs(strippedInputTypes) ? ASTRING : AUnionType.createUnknownableType(ASTRING);
     }
 
     private static boolean validArgs(IAType... strippedInputTypes) {
diff --git a/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/AbstractConcatStringEval.java b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/AbstractConcatStringEval.java
new file mode 100644
index 0000000..a61d96e
--- /dev/null
+++ b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/AbstractConcatStringEval.java
@@ -0,0 +1,376 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.runtime.evaluators.functions;
+
+import static org.apache.asterix.om.types.EnumDeserializer.ATYPETAGDESERIALIZER;
+
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.apache.asterix.common.annotations.MissingNullInOutFunction;
+import org.apache.asterix.om.exceptions.ExceptionUtil;
+import org.apache.asterix.om.types.ATypeTag;
+import org.apache.asterix.runtime.evaluators.common.ListAccessor;
+import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
+import org.apache.hyracks.algebricks.runtime.base.IEvaluatorContext;
+import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluator;
+import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluatorFactory;
+import org.apache.hyracks.api.exceptions.HyracksDataException;
+import org.apache.hyracks.api.exceptions.SourceLocation;
+import org.apache.hyracks.data.std.api.IPointable;
+import org.apache.hyracks.data.std.primitive.VoidPointable;
+import org.apache.hyracks.data.std.util.ArrayBackedValueStorage;
+import org.apache.hyracks.dataflow.common.data.accessors.IFrameTupleReference;
+import org.apache.hyracks.util.string.UTF8StringUtil;
+
+/**
+ * This evaluator performs string concatenation. It allows concatenating with or without a separator between each
+ * concatenation token. It also can be controlled to allow strings and lists of strings, or list of strings only as
+ * an input, based on the provided arguments.
+ *
+ * This evaluator is used by multiple functions that allow different arguments setup. This information is passed
+ * to the evaluator using the {@code isAcceptListOnly} and {@code separatorPosition} arguments.
+ */
+@MissingNullInOutFunction
+public abstract class AbstractConcatStringEval extends AbstractScalarEval {
+
+    // Different functions provide different argument position for the separator, this value indicates at which
+    // position the separator resides, if NO_SEPARATOR_POSITION value is provided, it means this function does not use
+    // a separator
+    private final int separatorPosition;
+    static final int NO_SEPARATOR_POSITION = -1;
+
+    // Context
+    private final IEvaluatorContext ctx;
+
+    // Storage
+    final ArrayBackedValueStorage storage = new ArrayBackedValueStorage();
+    final DataOutput storageDataOutput = storage.getDataOutput();
+
+    // Evaluators
+    private final IScalarEvaluator[] evals;
+    private final IPointable[] pointables;
+    protected ListAccessor listAccessor = new ListAccessor();
+
+    private final byte[] tempLengthArray = new byte[5];
+
+    // For handling missing, null, invalid data and warnings
+    protected boolean isMissingMet = false;
+    protected boolean isReturnNull = false;
+    protected int argIndex = -1;
+    protected byte[] expectedType = null;
+    protected ATypeTag unsupportedType = null;
+
+    protected final byte[] STRING_TYPE = new byte[] { ATypeTag.SERIALIZED_STRING_TYPE_TAG };
+
+    public AbstractConcatStringEval(IScalarEvaluatorFactory[] args, IEvaluatorContext ctx,
+            SourceLocation sourceLocation, FunctionIdentifier functionIdentifier, int separatorPosition)
+            throws HyracksDataException {
+        super(sourceLocation, functionIdentifier);
+        this.ctx = ctx;
+        this.separatorPosition = separatorPosition;
+
+        // Evaluators and Pointables
+        pointables = new IPointable[args.length];
+        evals = new IScalarEvaluator[args.length];
+        for (int i = 0; i < args.length; i++) {
+            evals[i] = args[i].createScalarEvaluator(ctx);
+            pointables[i] = new VoidPointable();
+        }
+    }
+
+    protected abstract boolean isAcceptedType(ATypeTag typeTag);
+
+    protected abstract byte[] getExpectedType();
+
+    @Override
+    public void evaluate(IFrameTupleReference tuple, IPointable result) throws HyracksDataException {
+        // Reset the members
+        resetValidationVariables();
+
+        // Evaluate and check the arguments (Missing/Null checks)
+        for (int i = 0; i < evals.length; i++) {
+            evals[i].evaluate(tuple, pointables[i]);
+            isMissingMet = doMissingNullCheck(result, pointables[i]);
+            if (isMissingMet) {
+                return;
+            }
+        }
+
+        // Terminate execution if any null outer argument has been encountered
+        if (isReturnNull) {
+            PointableHelper.setNull(result);
+            return;
+        }
+
+        byte[] separatorBytes = null;
+        int separatorStartOffset = 0;
+
+        // Validate the separator type (if it is present)
+        if (separatorPosition != NO_SEPARATOR_POSITION) {
+            separatorBytes = pointables[separatorPosition].getByteArray();
+            separatorStartOffset = pointables[separatorPosition].getStartOffset();
+            ATypeTag separatorTypeTag = ATYPETAGDESERIALIZER.deserialize(separatorBytes[separatorStartOffset]);
+
+            if (separatorTypeTag != ATypeTag.STRING) {
+                isReturnNull = true;
+                argIndex = separatorPosition;
+                expectedType = STRING_TYPE;
+                unsupportedType = separatorTypeTag;
+            }
+        }
+
+        // Rest of arguments check
+        for (int i = 0; i < pointables.length; i++) {
+            // Separator can come at different positions, so when validating other arguments, skip separator position
+            if (i != separatorPosition) {
+                byte[] bytes = pointables[i].getByteArray();
+                int startOffset = pointables[i].getStartOffset();
+                ATypeTag typeTag = ATYPETAGDESERIALIZER.deserialize(bytes[startOffset]);
+
+                if (!isAcceptedType(typeTag)) {
+                    isReturnNull = true;
+                    if (unsupportedType == null) {
+                        argIndex = i;
+                        expectedType = getExpectedType();
+                        unsupportedType = typeTag;
+                    }
+                }
+
+                // If the item is a list, we are checking list elements here
+                if (typeTag.isListType()) {
+                    listAccessor.reset(bytes, startOffset);
+
+                    for (int j = 0; j < listAccessor.size(); j++) {
+                        int itemStartOffset = listAccessor.getItemOffset(j);
+                        ATypeTag itemTypeTag = listAccessor.getItemType(itemStartOffset);
+
+                        if (itemTypeTag != ATypeTag.STRING) {
+                            if (itemTypeTag == ATypeTag.MISSING) {
+                                PointableHelper.setMissing(result);
+                                return;
+                            }
+
+                            isReturnNull = true;
+                            if (unsupportedType == null || itemTypeTag == ATypeTag.NULL) {
+                                argIndex = getActualArgumentIndex(i, j);
+                                expectedType = getExpectedType();
+                                unsupportedType = itemTypeTag;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        if (isReturnNull) {
+            PointableHelper.setNull(result);
+            if (unsupportedType != null && unsupportedType != ATypeTag.NULL) {
+                ExceptionUtil.warnTypeMismatch(ctx, sourceLoc, functionIdentifier, unsupportedType.serialize(),
+                        argIndex, expectedType);
+            }
+            return;
+        }
+
+        // Concatenate the strings
+        int utf8Length = calculateStringLength(separatorBytes, separatorStartOffset);
+        constructConcatenatedString(utf8Length, separatorBytes, separatorStartOffset);
+
+        result.set(storage);
+    }
+
+    /**
+     * Resets the validation variables to their default values at the start of each tuple run.
+     */
+    private void resetValidationVariables() {
+        isMissingMet = false;
+        isReturnNull = false;
+        argIndex = -1;
+        expectedType = null;
+        unsupportedType = null;
+    }
+
+    /**
+     * Performs the missing/null check.
+     *
+     * @param result result pointable
+     * @param pointable pointable to be checked
+     *
+     * @return {@code true} if execution should stop (missing encountered), {@code false} otherwise.
+     * @throws HyracksDataException Hyracks data exception
+     */
+    protected boolean doMissingNullCheck(IPointable result, IPointable pointable) throws HyracksDataException {
+        if (PointableHelper.checkAndSetMissingOrNull(result, pointable)) {
+            if (result.getByteArray()[result.getStartOffset()] == ATypeTag.SERIALIZED_MISSING_TYPE_TAG) {
+                return true;
+            }
+
+            // null value, but check other arguments for missing first (higher priority)
+            isReturnNull = true;
+        }
+        return false;
+    }
+
+    /**
+     * One of the functions using this evaluator has its arguments (multiple arguments) internally converted into a
+     * single argument as a list of arguments. This method can be overridden to use the position in the list to return
+     * the correct position of the argument when reporting back.
+     */
+    int getActualArgumentIndex(int outArgumentIndex, int innerArgumentIndex) {
+        return outArgumentIndex;
+    }
+
+    /**
+     * Calculates the required total length for the generated string result
+     *
+     * @return The total string length
+     */
+    private int calculateStringLength(final byte[] separatorBytes, final int separatorStartOffset)
+            throws HyracksDataException {
+        int utf8Length = 0;
+
+        // Separator length is fixed, calculate it once only (if present)
+        int separatorLength = 0;
+        if (separatorPosition != NO_SEPARATOR_POSITION) {
+            separatorLength = UTF8StringUtil.getUTFLength(separatorBytes, separatorStartOffset + 1);
+        }
+
+        for (int i = 0; i < pointables.length; i++) {
+            // Skip separator position
+            if (i != separatorPosition) {
+                byte[] bytes = pointables[i].getByteArray();
+                int startOffset = pointables[i].getStartOffset();
+                ATypeTag typeTag = ATYPETAGDESERIALIZER.deserialize(bytes[startOffset]);
+
+                // Second argument is a string or array
+                // Calculate total string length (string has variable length)
+                try {
+                    if (typeTag == ATypeTag.STRING) {
+                        startOffset++; // skip the type tag byte
+                        utf8Length += UTF8StringUtil.getUTFLength(bytes, startOffset);
+                    } else {
+                        listAccessor.reset(bytes, startOffset);
+
+                        for (int j = 0; j < listAccessor.size(); j++) {
+                            int itemStartOffset = listAccessor.getItemOffset(j);
+
+                            if (listAccessor.itemsAreSelfDescribing()) {
+                                itemStartOffset++;
+                            }
+
+                            utf8Length += UTF8StringUtil.getUTFLength(bytes, itemStartOffset);
+
+                            // Ensure separator is present before applying the separator calculation
+                            // Separator length (on last item, do not add separator length)
+                            if (separatorPosition != NO_SEPARATOR_POSITION && j < listAccessor.size() - 1) {
+                                utf8Length += separatorLength;
+                            }
+                        }
+                    }
+                } catch (IOException ex) {
+                    throw HyracksDataException.create(ex);
+                }
+
+                // Ensure separator is present before applying the separator calculation
+                // Separator length (on last item, do not add separator length)
+                // Depending on separator position (start or end), we need to adjust the calculation
+                if (separatorPosition != NO_SEPARATOR_POSITION
+                        && i < pointables.length - (separatorPosition > 0 ? 2 : 1)) {
+                    utf8Length += separatorLength;
+                }
+            }
+        }
+
+        return utf8Length;
+    }
+
+    /**
+     * Constructs the concatenated string from the provided strings
+     */
+    private void constructConcatenatedString(final int utf8Length, final byte[] separatorBytes,
+            final int separatorStartOffset) throws HyracksDataException {
+        // Arguments validation is done in the length calculation step
+        try {
+            Arrays.fill(tempLengthArray, (byte) 0);
+            int cbytes = UTF8StringUtil.encodeUTF8Length(utf8Length, tempLengthArray, 0);
+            storage.reset();
+            storageDataOutput.writeByte(ATypeTag.SERIALIZED_STRING_TYPE_TAG);
+            storageDataOutput.write(tempLengthArray, 0, cbytes);
+
+            // Separator length is fixed, calculate it once only (if present)
+            int separatorLength = 0;
+            int separatorMetaLength = 0;
+            if (separatorPosition != NO_SEPARATOR_POSITION) {
+                separatorLength = UTF8StringUtil.getUTFLength(separatorBytes, separatorStartOffset + 1);
+                separatorMetaLength = UTF8StringUtil.getNumBytesToStoreLength(separatorLength);
+            }
+
+            int StringLength;
+            for (int i = 0; i < pointables.length; i++) {
+                // Skip separator position
+                if (i != separatorPosition) {
+                    byte[] bytes = pointables[i].getByteArray();
+                    int startOffset = pointables[i].getStartOffset();
+                    ATypeTag typeTag = ATYPETAGDESERIALIZER.deserialize(bytes[startOffset]);
+
+                    if (typeTag == ATypeTag.STRING) {
+                        startOffset++; // skip the type tag byte
+                        StringLength = UTF8StringUtil.getUTFLength(bytes, startOffset);
+                        storageDataOutput.write(bytes,
+                                UTF8StringUtil.getNumBytesToStoreLength(StringLength) + startOffset, StringLength);
+                    } else {
+                        listAccessor.reset(bytes, startOffset);
+
+                        for (int j = 0; j < listAccessor.size(); j++) {
+                            int itemStartOffset = listAccessor.getItemOffset(j);
+
+                            if (listAccessor.itemsAreSelfDescribing()) {
+                                itemStartOffset++;
+                            }
+
+                            StringLength = UTF8StringUtil.getUTFLength(bytes, itemStartOffset);
+                            storageDataOutput.write(bytes,
+                                    UTF8StringUtil.getNumBytesToStoreLength(StringLength) + itemStartOffset,
+                                    StringLength);
+
+                            // Ensure separator is present before applying the separator calculation
+                            // More arguments, add a separator
+                            if (separatorPosition != NO_SEPARATOR_POSITION && j < listAccessor.size() - 1) {
+                                storageDataOutput.write(separatorBytes, separatorMetaLength + separatorStartOffset + 1,
+                                        separatorLength);
+                            }
+                        }
+                    }
+
+                    // Ensure separator is present before applying the separator calculation
+                    // More arguments, add a separator
+                    // Depending on separator position (start or end), we need to adjust the calculation
+                    if (separatorPosition != NO_SEPARATOR_POSITION
+                            && i < pointables.length - (separatorPosition > 0 ? 2 : 1)) {
+                        storageDataOutput.write(separatorBytes, separatorMetaLength + separatorStartOffset + 1,
+                                separatorLength);
+                    }
+                }
+            }
+        } catch (IOException ex) {
+            throw HyracksDataException.create(ex);
+        }
+    }
+}
diff --git a/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/PointableHelper.java b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/PointableHelper.java
index 56dd802..01aa181 100644
--- a/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/PointableHelper.java
+++ b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/PointableHelper.java
@@ -18,6 +18,8 @@
  */
 package org.apache.asterix.runtime.evaluators.functions;
 
+import static org.apache.asterix.om.types.EnumDeserializer.ATYPETAGDESERIALIZER;
+
 import java.io.DataOutput;
 import java.io.IOException;
 import java.util.Collection;
@@ -28,6 +30,7 @@
 import org.apache.asterix.om.types.ATypeTag;
 import org.apache.asterix.om.types.EnumDeserializer;
 import org.apache.asterix.om.types.hierachy.ATypeHierarchy;
+import org.apache.asterix.runtime.evaluators.common.ListAccessor;
 import org.apache.hyracks.api.dataflow.value.IBinaryComparator;
 import org.apache.hyracks.api.exceptions.HyracksDataException;
 import org.apache.hyracks.data.std.api.IMutableValueStorage;
@@ -164,20 +167,46 @@
         pointable.set(MISSING_BYTES, 0, MISSING_BYTES.length);
     }
 
-    // checkAndSetMissingOrNull with 1 argument
-    public static boolean checkAndSetMissingOrNull(IPointable result, IPointable pointable1) {
-        return checkAndSetMissingOrNull(result, pointable1, null, null, null);
+    // 1 pointable check
+    public static boolean checkAndSetMissingOrNull(IPointable result, IPointable pointable1)
+            throws HyracksDataException {
+        return checkAndSetMissingOrNull(result, null, pointable1, null, null, null);
     }
 
-    // checkAndSetMissingOrNull with 2 arguments
-    public static boolean checkAndSetMissingOrNull(IPointable result, IPointable pointable1, IPointable pointable2) {
-        return checkAndSetMissingOrNull(result, pointable1, pointable2, null, null);
+    // 2 pointables check
+    public static boolean checkAndSetMissingOrNull(IPointable result, IPointable pointable1, IPointable pointable2)
+            throws HyracksDataException {
+        return checkAndSetMissingOrNull(result, null, pointable1, pointable2, null, null);
     }
 
-    // checkAndSetMissingOrNull with 3 arguments
+    // 3 pointables check
     public static boolean checkAndSetMissingOrNull(IPointable result, IPointable pointable1, IPointable pointable2,
-            IPointable pointable3) {
-        return checkAndSetMissingOrNull(result, pointable1, pointable2, pointable3, null);
+            IPointable pointable3) throws HyracksDataException {
+        return checkAndSetMissingOrNull(result, null, pointable1, pointable2, pointable3, null);
+    }
+
+    // 4 pointables check
+    public static boolean checkAndSetMissingOrNull(IPointable result, IPointable pointable1, IPointable pointable2,
+            IPointable pointable3, IPointable pointable4) throws HyracksDataException {
+        return checkAndSetMissingOrNull(result, null, pointable1, pointable2, pointable3, pointable4);
+    }
+
+    // 1 pointable check (check list members for missing values)
+    public static boolean checkAndSetMissingOrNull(IPointable result, ListAccessor listAccessor, IPointable pointable1)
+            throws HyracksDataException {
+        return checkAndSetMissingOrNull(result, listAccessor, pointable1, null, null, null);
+    }
+
+    // 2 pointables check (check list members for missing values)
+    public static boolean checkAndSetMissingOrNull(IPointable result, ListAccessor listAccessor, IPointable pointable1,
+            IPointable pointable2) throws HyracksDataException {
+        return checkAndSetMissingOrNull(result, listAccessor, pointable1, pointable2, null, null);
+    }
+
+    // 3 pointables check (check list members for missing values)
+    public static boolean checkAndSetMissingOrNull(IPointable result, ListAccessor listAccessor, IPointable pointable1,
+            IPointable pointable2, IPointable pointable3) throws HyracksDataException {
+        return checkAndSetMissingOrNull(result, listAccessor, pointable1, pointable2, pointable3, null);
     }
 
     /**
@@ -188,7 +217,12 @@
      * As the missing encounter has a higher priority than the null, the method will keep checking if any missing has
      * been encountered first, if not, it will do a null check at the end.
      *
+     * If the listAccessor is passed, this method will also go through any list pointable elements and search for
+     * a missing value to give it a higher priority over null values. If {@code null} is passed for the listAccessor,
+     * the list element check will be skipped.
+     *
      * @param result the result pointable that will hold the data
+     * @param listAccessor list accessor to use for check list elements.
      * @param pointable1 the first pointable to be checked
      * @param pointable2 the second pointable to be checked
      * @param pointable3 the third pointable to be checked
@@ -196,13 +230,13 @@
      *
      * @return {@code true} if the pointable value is missing or null, {@code false} otherwise.
      */
-    public static boolean checkAndSetMissingOrNull(IPointable result, IPointable pointable1, IPointable pointable2,
-            IPointable pointable3, IPointable pointable4) {
+    public static boolean checkAndSetMissingOrNull(IPointable result, ListAccessor listAccessor, IPointable pointable1,
+            IPointable pointable2, IPointable pointable3, IPointable pointable4) throws HyracksDataException {
 
         // this flag will keep an eye on whether a null value is encountered or not
         boolean isMeetNull = false;
 
-        switch (getPointableValueState(pointable1)) {
+        switch (getPointableValueState(pointable1, listAccessor)) {
             case MISSING:
                 setMissing(result);
                 return true;
@@ -212,7 +246,7 @@
         }
 
         if (pointable2 != null) {
-            switch (getPointableValueState(pointable2)) {
+            switch (getPointableValueState(pointable2, listAccessor)) {
                 case MISSING:
                     setMissing(result);
                     return true;
@@ -223,7 +257,7 @@
         }
 
         if (pointable3 != null) {
-            switch (getPointableValueState(pointable3)) {
+            switch (getPointableValueState(pointable3, listAccessor)) {
                 case MISSING:
                     setMissing(result);
                     return true;
@@ -234,7 +268,7 @@
         }
 
         if (pointable4 != null) {
-            switch (getPointableValueState(pointable4)) {
+            switch (getPointableValueState(pointable4, listAccessor)) {
                 case MISSING:
                     setMissing(result);
                     return true;
@@ -257,23 +291,51 @@
     /**
      * This method checks and returns the pointable value state.
      *
+     * If a ListAccessor is passed to this function, it will check if the passed pointable is a list, and if so, it
+     * will search for a missing value inside the list before checking for null values. If the listAccessor value is
+     * null, no list elements check will be performed.
+     *
      * @param pointable the pointable to be checked
+     * @param listAccessor list accessor used to check the list elements.
      *
      * @return the pointable value state for the passed pointable
      */
-    private static PointableValueState getPointableValueState(IPointable pointable) {
+    private static PointableValueState getPointableValueState(IPointable pointable, ListAccessor listAccessor)
+            throws HyracksDataException {
         if (pointable.getLength() == 0) {
             return PointableValueState.EMPTY_POINTABLE;
         }
 
         byte[] bytes = pointable.getByteArray();
         int offset = pointable.getStartOffset();
+        ATypeTag typeTag = ATYPETAGDESERIALIZER.deserialize(bytes[offset]);
 
-        if (bytes[offset] == ATypeTag.SERIALIZED_MISSING_TYPE_TAG) {
+        if (typeTag == ATypeTag.MISSING) {
             return PointableValueState.MISSING;
         }
 
-        if (bytes[offset] == ATypeTag.SERIALIZED_NULL_TYPE_TAG) {
+        if (typeTag == ATypeTag.NULL) {
+            return PointableValueState.NULL;
+        }
+
+        boolean isNull = false;
+
+        // Check the list elements first as it may have a missing that needs to be reported first
+        if (listAccessor != null && typeTag.isListType()) {
+            listAccessor.reset(bytes, offset);
+
+            for (int i = 0; i < listAccessor.size(); i++) {
+                if (listAccessor.getItemType(listAccessor.getItemOffset(i)) == ATypeTag.MISSING) {
+                    return PointableValueState.MISSING;
+                }
+
+                if (listAccessor.getItemType(listAccessor.getItemOffset(i)) == ATypeTag.NULL) {
+                    isNull = true;
+                }
+            }
+        }
+
+        if (isNull) {
             return PointableValueState.NULL;
         }
 
diff --git a/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/StringConcatDescriptor.java b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/StringConcatDescriptor.java
index e16ab51..319b615 100644
--- a/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/StringConcatDescriptor.java
+++ b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/StringConcatDescriptor.java
@@ -18,31 +18,16 @@
  */
 package org.apache.asterix.runtime.evaluators.functions;
 
-import java.io.DataOutput;
-import java.io.IOException;
-
 import org.apache.asterix.common.annotations.MissingNullInOutFunction;
-import org.apache.asterix.formats.nontagged.SerializerDeserializerProvider;
-import org.apache.asterix.om.base.AMissing;
-import org.apache.asterix.om.base.ANull;
-import org.apache.asterix.om.exceptions.ExceptionUtil;
 import org.apache.asterix.om.functions.BuiltinFunctions;
 import org.apache.asterix.om.functions.IFunctionDescriptorFactory;
 import org.apache.asterix.om.types.ATypeTag;
-import org.apache.asterix.om.types.BuiltinType;
 import org.apache.asterix.runtime.evaluators.base.AbstractScalarFunctionDynamicDescriptor;
-import org.apache.asterix.runtime.evaluators.common.ListAccessor;
 import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
 import org.apache.hyracks.algebricks.runtime.base.IEvaluatorContext;
 import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluator;
 import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluatorFactory;
-import org.apache.hyracks.api.dataflow.value.ISerializerDeserializer;
 import org.apache.hyracks.api.exceptions.HyracksDataException;
-import org.apache.hyracks.data.std.api.IPointable;
-import org.apache.hyracks.data.std.primitive.VoidPointable;
-import org.apache.hyracks.data.std.util.ArrayBackedValueStorage;
-import org.apache.hyracks.dataflow.common.data.accessors.IFrameTupleReference;
-import org.apache.hyracks.util.string.UTF8StringUtil;
 
 @MissingNullInOutFunction
 public class StringConcatDescriptor extends AbstractScalarFunctionDynamicDescriptor {
@@ -57,98 +42,31 @@
 
             @Override
             public IScalarEvaluator createScalarEvaluator(final IEvaluatorContext ctx) throws HyracksDataException {
-                return new IScalarEvaluator() {
-
-                    private final ArrayBackedValueStorage resultStorage = new ArrayBackedValueStorage();
-                    private final ListAccessor listAccessor = new ListAccessor();
-                    private final DataOutput out = resultStorage.getDataOutput();
-                    private final IScalarEvaluatorFactory listEvalFactory = args[0];
-                    private final IPointable inputArgList = new VoidPointable();
-                    private final IScalarEvaluator evalList = listEvalFactory.createScalarEvaluator(ctx);
-                    @SuppressWarnings("unchecked")
-                    private ISerializerDeserializer<ANull> nullSerde =
-                            SerializerDeserializerProvider.INSTANCE.getSerializerDeserializer(BuiltinType.ANULL);
-                    @SuppressWarnings("unchecked")
-                    private ISerializerDeserializer<AMissing> missingSerde =
-                            SerializerDeserializerProvider.INSTANCE.getSerializerDeserializer(BuiltinType.AMISSING);
-                    private final byte[] tempLengthArray = new byte[5];
-                    private final byte[] expectedTypes = new byte[] { ATypeTag.SERIALIZED_ORDEREDLIST_TYPE_TAG,
-                            ATypeTag.SERIALIZED_UNORDEREDLIST_TYPE_TAG };
+                return new AbstractConcatStringEval(args, ctx, sourceLoc, getIdentifier(),
+                        AbstractConcatStringEval.NO_SEPARATOR_POSITION) {
 
                     @Override
-                    public void evaluate(IFrameTupleReference tuple, IPointable result) throws HyracksDataException {
-                        resultStorage.reset();
-                        try {
-                            evalList.evaluate(tuple, inputArgList);
+                    protected boolean isAcceptedType(ATypeTag typeTag) {
+                        return typeTag.isListType();
+                    }
 
-                            if (PointableHelper.checkAndSetMissingOrNull(result, inputArgList)) {
-                                return;
-                            }
+                    @Override
+                    protected byte[] getExpectedType() {
+                        return STRING_TYPE;
+                    }
 
-                            byte[] listBytes = inputArgList.getByteArray();
-                            int listOffset = inputArgList.getStartOffset();
-
-                            if (listBytes[listOffset] != ATypeTag.SERIALIZED_ORDEREDLIST_TYPE_TAG
-                                    && listBytes[listOffset] != ATypeTag.SERIALIZED_UNORDEREDLIST_TYPE_TAG) {
-                                PointableHelper.setNull(result);
-                                ExceptionUtil.warnTypeMismatch(ctx, sourceLoc, getIdentifier(), listBytes[listOffset],
-                                        0, expectedTypes);
-                                return;
-                            }
-                            listAccessor.reset(listBytes, listOffset);
-                            // calculate length first
-                            int utf8Len = 0;
-                            boolean itemIsNull = false;
-                            ATypeTag unsupportedType = null;
-                            for (int i = 0; i < listAccessor.size(); i++) {
-                                int itemOffset = listAccessor.getItemOffset(i);
-                                ATypeTag itemType = listAccessor.getItemType(itemOffset);
-                                // Increase the offset by 1 if the give list has heterogeneous elements,
-                                // since the item itself has a typetag.
-                                if (listAccessor.itemsAreSelfDescribing()) {
-                                    itemOffset += 1;
-                                }
-                                if (itemType != ATypeTag.STRING) {
-                                    if (itemType == ATypeTag.NULL) {
-                                        itemIsNull = true;
-                                        continue;
-                                    }
-                                    if (itemType == ATypeTag.MISSING) {
-                                        missingSerde.serialize(AMissing.MISSING, out);
-                                        result.set(resultStorage);
-                                        return;
-                                    }
-                                    if (unsupportedType == null) {
-                                        unsupportedType = itemType;
-                                    }
-                                }
-                                utf8Len += UTF8StringUtil.getUTFLength(listBytes, itemOffset);
-                            }
-                            if (itemIsNull || unsupportedType != null) {
-                                nullSerde.serialize(ANull.NULL, out);
-                                result.set(resultStorage);
-                                if (unsupportedType != null) {
-                                    ExceptionUtil.warnUnsupportedType(ctx, sourceLoc, getIdentifier().getName(),
-                                            unsupportedType);
-                                }
-                                return;
-                            }
-                            out.writeByte(ATypeTag.SERIALIZED_STRING_TYPE_TAG);
-                            int cbytes = UTF8StringUtil.encodeUTF8Length(utf8Len, tempLengthArray, 0);
-                            out.write(tempLengthArray, 0, cbytes);
-                            for (int i = 0; i < listAccessor.size(); i++) {
-                                int itemOffset = listAccessor.getItemOffset(i);
-                                if (listAccessor.itemsAreSelfDescribing()) {
-                                    itemOffset += 1;
-                                }
-                                utf8Len = UTF8StringUtil.getUTFLength(listBytes, itemOffset);
-                                out.write(listBytes, UTF8StringUtil.getNumBytesToStoreLength(utf8Len) + itemOffset,
-                                        utf8Len);
-                            }
-                        } catch (IOException e) {
-                            throw HyracksDataException.create(e);
-                        }
-                        result.set(resultStorage);
+                    /**
+                     * This function has its arguments converted into a single list of arguments, the inner index
+                     * can be used to point to the correct argument for user perspective.
+                     *
+                     * @param outArgumentIndex outer argument index
+                     * @param innerArgumentIndex inner argument index (inside a list)
+                     *
+                     * @return the actual index of the current argument
+                     */
+                    @Override
+                    int getActualArgumentIndex(int outArgumentIndex, int innerArgumentIndex) {
+                        return innerArgumentIndex;
                     }
                 };
             }
diff --git a/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/StringJoinDescriptor.java b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/StringJoinDescriptor.java
index 0c39fae..960ac5c 100644
--- a/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/StringJoinDescriptor.java
+++ b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/StringJoinDescriptor.java
@@ -18,27 +18,33 @@
  */
 package org.apache.asterix.runtime.evaluators.functions;
 
-import java.io.DataOutput;
-import java.io.IOException;
-
 import org.apache.asterix.common.annotations.MissingNullInOutFunction;
-import org.apache.asterix.om.exceptions.ExceptionUtil;
 import org.apache.asterix.om.functions.BuiltinFunctions;
 import org.apache.asterix.om.functions.IFunctionDescriptorFactory;
 import org.apache.asterix.om.types.ATypeTag;
 import org.apache.asterix.runtime.evaluators.base.AbstractScalarFunctionDynamicDescriptor;
-import org.apache.asterix.runtime.evaluators.common.ListAccessor;
 import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
 import org.apache.hyracks.algebricks.runtime.base.IEvaluatorContext;
 import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluator;
 import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluatorFactory;
 import org.apache.hyracks.api.exceptions.HyracksDataException;
-import org.apache.hyracks.data.std.api.IPointable;
-import org.apache.hyracks.data.std.primitive.VoidPointable;
-import org.apache.hyracks.data.std.util.ArrayBackedValueStorage;
-import org.apache.hyracks.dataflow.common.data.accessors.IFrameTupleReference;
-import org.apache.hyracks.util.string.UTF8StringUtil;
 
+/**
+ * This function takes 2 arguments, first argument is a list of strings, and second argument is a string separator,
+ * the function concatenates the strings in the first list argument together and returns that as a result.
+ *
+ * If only a single string is passed in the list, the separator is not added to the result.
+ *
+ * The behavior is as follows:
+ * - If the first argument is a list of strings, and the second argument is a string, concatenated string is returned.
+ * - If any argument is missing, missing is returned.
+ * - If any argument is null, null is returned.
+ * - If any item is not a string, or the array is containing non-strings, null is returned.
+ *
+ * Examples:
+ * string_join(["1", "2"], "-") -> "1-2"
+ * string_join(["1"], "-") -> "1"
+ */
 @MissingNullInOutFunction
 public class StringJoinDescriptor extends AbstractScalarFunctionDynamicDescriptor {
 
@@ -52,102 +58,19 @@
 
             @Override
             public IScalarEvaluator createScalarEvaluator(final IEvaluatorContext ctx) throws HyracksDataException {
-                return new IScalarEvaluator() {
-                    private final ArrayBackedValueStorage resultStorage = new ArrayBackedValueStorage();
-                    private final ListAccessor listAccessor = new ListAccessor();
-                    private final DataOutput out = resultStorage.getDataOutput();
-                    private final IScalarEvaluatorFactory listEvalFactory = args[0];
-                    private final IScalarEvaluatorFactory sepEvalFactory = args[1];
-                    private final IPointable inputArgList = new VoidPointable();
-                    private final IPointable inputArgSep = new VoidPointable();
-                    private final IScalarEvaluator evalList = listEvalFactory.createScalarEvaluator(ctx);
-                    private final IScalarEvaluator evalSep = sepEvalFactory.createScalarEvaluator(ctx);
-                    private final byte[] tempLengthArray = new byte[5];
-                    private final byte[] expectedTypeList =
-                            { ATypeTag.SERIALIZED_ORDEREDLIST_TYPE_TAG, ATypeTag.SERIALIZED_UNORDEREDLIST_TYPE_TAG };
+                return new AbstractConcatStringEval(args, ctx, sourceLoc, getIdentifier(), 1) {
+
+                    // TODO Need a way to report array of strings is the expected type
+                    private final byte[] ARRAY_TYPE = new byte[] { ATypeTag.SERIALIZED_ORDEREDLIST_TYPE_TAG };
 
                     @Override
-                    public void evaluate(IFrameTupleReference tuple, IPointable result) throws HyracksDataException {
-                        resultStorage.reset();
-                        evalList.evaluate(tuple, inputArgList);
-                        evalSep.evaluate(tuple, inputArgSep);
+                    protected boolean isAcceptedType(ATypeTag typeTag) {
+                        return typeTag.isListType();
+                    }
 
-                        if (PointableHelper.checkAndSetMissingOrNull(result, inputArgList, inputArgSep)) {
-                            return;
-                        }
-
-                        byte[] listBytes = inputArgList.getByteArray();
-                        int listOffset = inputArgList.getStartOffset();
-                        if (listBytes[listOffset] != ATypeTag.SERIALIZED_ORDEREDLIST_TYPE_TAG
-                                && listBytes[listOffset] != ATypeTag.SERIALIZED_UNORDEREDLIST_TYPE_TAG) {
-                            PointableHelper.setNull(result);
-                            ExceptionUtil.warnTypeMismatch(ctx, sourceLoc, getIdentifier(), listBytes[listOffset], 0,
-                                    expectedTypeList);
-                            return;
-                        }
-                        byte[] sepBytes = inputArgSep.getByteArray();
-                        int sepOffset = inputArgSep.getStartOffset();
-                        if (sepBytes[sepOffset] != ATypeTag.SERIALIZED_STRING_TYPE_TAG) {
-                            PointableHelper.setNull(result);
-                            ExceptionUtil.warnTypeMismatch(ctx, sourceLoc, getIdentifier(), sepBytes[sepOffset], 1,
-                                    ATypeTag.STRING);
-                            return;
-                        }
-                        int sepLen = UTF8StringUtil.getUTFLength(sepBytes, sepOffset + 1);
-                        int sepMetaLen = UTF8StringUtil.getNumBytesToStoreLength(sepLen);
-
-                        listAccessor.reset(listBytes, listOffset);
-                        try {
-                            // calculate length first
-                            int utf8Len = 0;
-                            int size = listAccessor.size();
-                            for (int i = 0; i < size; i++) {
-                                int itemOffset = listAccessor.getItemOffset(i);
-                                ATypeTag itemType = listAccessor.getItemType(itemOffset);
-                                // Increase the offset by 1 if the give list has heterogeneous elements,
-                                // since the item itself has a typetag.
-                                if (listAccessor.itemsAreSelfDescribing()) {
-                                    itemOffset += 1;
-                                }
-                                if (itemType != ATypeTag.STRING) {
-                                    if (itemType == ATypeTag.MISSING) {
-                                        PointableHelper.setMissing(result);
-                                        return;
-                                    }
-                                    PointableHelper.setNull(result);
-                                    if (itemType != ATypeTag.NULL) {
-                                        // warn only if the call is: string_join([1,3], "/") where elements are non-null
-                                        ExceptionUtil.warnUnsupportedType(ctx, sourceLoc, getIdentifier().getName(),
-                                                itemType);
-                                    }
-                                    return;
-                                }
-                                int currentSize = UTF8StringUtil.getUTFLength(listBytes, itemOffset);
-                                if (i != size - 1 && currentSize != 0) {
-                                    utf8Len += sepLen;
-                                }
-                                utf8Len += currentSize;
-                            }
-
-                            out.writeByte(ATypeTag.SERIALIZED_STRING_TYPE_TAG);
-                            int cbytes = UTF8StringUtil.encodeUTF8Length(utf8Len, tempLengthArray, 0);
-                            out.write(tempLengthArray, 0, cbytes);
-                            for (int i = 0; i < listAccessor.size(); i++) {
-                                int itemOffset = listAccessor.getItemOffset(i);
-                                if (listAccessor.itemsAreSelfDescribing()) {
-                                    itemOffset += 1;
-                                }
-                                utf8Len = UTF8StringUtil.getUTFLength(listBytes, itemOffset);
-                                out.write(listBytes, UTF8StringUtil.getNumBytesToStoreLength(utf8Len) + itemOffset,
-                                        utf8Len);
-                                for (int j = 0; j < sepLen; j++) {
-                                    out.writeByte(sepBytes[sepOffset + 1 + sepMetaLen + j]);
-                                }
-                            }
-                        } catch (IOException ex) {
-                            throw HyracksDataException.create(ex);
-                        }
-                        result.set(resultStorage);
+                    @Override
+                    protected byte[] getExpectedType() {
+                        return ARRAY_TYPE;
                     }
                 };
             }
@@ -158,4 +81,4 @@
     public FunctionIdentifier getIdentifier() {
         return BuiltinFunctions.STRING_JOIN;
     }
-}
+}
\ No newline at end of file