>From Peeyush Gupta <[email protected]>:

Peeyush Gupta has uploaded this change for review. ( 
https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/20800?usp=email )


Change subject: DO NOT MERGE: curl function (AI written)
......................................................................

DO NOT MERGE: curl function (AI written)

Change-Id: I346e01f1e05a2bb6ce7aef2c2f6393844eed3b2f
---
M asterixdb/asterix-app/src/test/resources/runtimets/only_sqlpp.xml
A 
asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/misc/curl/curl.1.query.sqlpp
A 
asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/misc/curl/curl.2.query.sqlpp
A 
asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/misc/curl/curl.3.query.sqlpp
A 
asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/misc/curl/curl.4.query.sqlpp
A 
asterixdb/asterix-app/src/test/resources/runtimets/results/misc/curl/curl.1.regex
A 
asterixdb/asterix-app/src/test/resources/runtimets/results/misc/curl/curl.2.regex
A 
asterixdb/asterix-app/src/test/resources/runtimets/results/misc/curl/curl.3.regex
A 
asterixdb/asterix-app/src/test/resources/runtimets/results/misc/curl/curl.4.regex
M asterixdb/asterix-app/src/test/resources/runtimets/sqlpp_queries.xml
M 
asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/BuiltinFunctions.java
A 
asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/CurlDescriptor.java
M 
asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/functions/FunctionCollection.java
13 files changed, 466 insertions(+), 4 deletions(-)



  git pull ssh://asterix-gerrit.ics.uci.edu:29418/asterixdb 
refs/changes/00/20800/1

diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/only_sqlpp.xml 
b/asterixdb/asterix-app/src/test/resources/runtimets/only_sqlpp.xml
index 334dd52..740b298 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/only_sqlpp.xml
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/only_sqlpp.xml
@@ -19,5 +19,10 @@
  !-->
 <test-suite xmlns="urn:xml.testframework.asterix.apache.org" 
ResultOffsetPath="results" QueryOffsetPath="queries_sqlpp" 
QueryFileExtension=".sqlpp">
   <test-group name="failed">
+    <test-case FilePath="misc">
+      <compilation-unit name="curl">
+        <output-dir compare="Text">curl</output-dir>
+      </compilation-unit>
+    </test-case>
   </test-group>
 </test-suite>
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/misc/curl/curl.1.query.sqlpp
 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/misc/curl/curl.1.query.sqlpp
new file mode 100644
index 0000000..bc5c555
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/misc/curl/curl.1.query.sqlpp
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+/*
+ * Description: Test curl function - Basic GET request
+ * Expected Res: Success - returns cluster info
+ */
+
+curl("http://localhost:19002/admin/cluster";);
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/misc/curl/curl.2.query.sqlpp
 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/misc/curl/curl.2.query.sqlpp
new file mode 100644
index 0000000..bdfc59b
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/misc/curl/curl.2.query.sqlpp
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+/*
+ * Description: Test curl function with explicit GET request option
+ * Expected Res: Success - returns cluster info
+ */
+
+curl("http://localhost:19002/admin/cluster";, {"request": "GET"});
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/misc/curl/curl.3.query.sqlpp
 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/misc/curl/curl.3.query.sqlpp
new file mode 100644
index 0000000..527abbf
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/misc/curl/curl.3.query.sqlpp
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+/*
+ * Description: Test curl function with custom headers
+ * Expected Res: Success - returns cluster info with custom header set
+ */
+
+curl("http://localhost:19002/admin/cluster";, {
+    "request": "GET",
+    "header": "Accept: application/json"
+});
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/misc/curl/curl.4.query.sqlpp
 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/misc/curl/curl.4.query.sqlpp
new file mode 100644
index 0000000..963c5d5
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/misc/curl/curl.4.query.sqlpp
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+/*
+ * Description: Test curl function with timeout settings
+ * Expected Res: Success - returns cluster info with custom timeout
+ */
+
+curl("http://localhost:19002/admin/cluster";, {
+    "request": "GET",
+    "connect-timeout": 3000,
+    "max-time": 5000
+});
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/results/misc/curl/curl.1.regex
 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/misc/curl/curl.1.regex
new file mode 100644
index 0000000..ab6a155
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/misc/curl/curl.1.regex
@@ -0,0 +1 @@
+/.*/
\ No newline at end of file
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/results/misc/curl/curl.2.regex
 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/misc/curl/curl.2.regex
new file mode 100644
index 0000000..5c30942
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/misc/curl/curl.2.regex
@@ -0,0 +1 @@
+/.*/
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/results/misc/curl/curl.3.regex
 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/misc/curl/curl.3.regex
new file mode 100644
index 0000000..5c30942
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/misc/curl/curl.3.regex
@@ -0,0 +1 @@
+/.*/
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/results/misc/curl/curl.4.regex
 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/misc/curl/curl.4.regex
new file mode 100644
index 0000000..5c30942
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/misc/curl/curl.4.regex
@@ -0,0 +1 @@
+/.*/
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/sqlpp_queries.xml 
b/asterixdb/asterix-app/src/test/resources/runtimets/sqlpp_queries.xml
index 5e6c654..966f3f6 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/sqlpp_queries.xml
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/sqlpp_queries.xml
@@ -7190,6 +7190,11 @@
   </test-group>
   <test-group name="misc">
     <test-case FilePath="misc">
+      <compilation-unit name="curl">
+        <output-dir compare="Text">curl</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="misc">
       <compilation-unit name="big_in_list/000">
         <output-dir compare="Text">big_in_list/000</output-dir>
       </compilation-unit>
diff --git 
a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/BuiltinFunctions.java
 
b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/BuiltinFunctions.java
index 51ad36b..295479a 100644
--- 
a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/BuiltinFunctions.java
+++ 
b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/BuiltinFunctions.java
@@ -407,6 +407,8 @@
     public static final FunctionIdentifier STRING_LPAD = 
FunctionConstants.newAsterix("lpad", 3);
     public static final FunctionIdentifier STRING_RPAD = 
FunctionConstants.newAsterix("rpad", 3);

+    public static final FunctionIdentifier CURL = 
FunctionConstants.newAsterix("curl", FunctionIdentifier.VARARGS);
+
     public static final FunctionIdentifier DATASET =
             FunctionConstants.newAsterix("dataset", 
FunctionIdentifier.VARARGS); // 1, 2 or 3
     public static final FunctionIdentifier FEED_COLLECT = 
FunctionConstants.newAsterix("feed-collect", 7);
@@ -1047,7 +1049,7 @@
     public static final FunctionIdentifier RANDOM = 
FunctionConstants.newAsterix("random", 0);
     public static final FunctionIdentifier RANDOM_WITH_SEED = 
FunctionConstants.newAsterix("random", 1);

-    //Geo
+    // Geo
     public static final FunctionIdentifier ST_AREA = 
FunctionConstants.newAsterix("st-area", 1);
     public static final FunctionIdentifier ST_MAKE_POINT = 
FunctionConstants.newAsterix("st-make-point", 2);
     public static final FunctionIdentifier ST_MAKE_POINT3D = 
FunctionConstants.newAsterix("st-make-point", 3);
@@ -1556,6 +1558,8 @@
         addFunction(STRING_LPAD, AStringTypeComputer.INSTANCE_NULLABLE, true);
         addFunction(STRING_RPAD, AStringTypeComputer.INSTANCE_NULLABLE, true);

+        addFunction(CURL, AStringTypeComputer.INSTANCE_NULLABLE, true);
+
         addPrivateFunction(ORDERED_LIST_CONSTRUCTOR, 
OrderedListConstructorTypeComputer.INSTANCE, true);
         addFunction(POINT_CONSTRUCTOR, APointTypeComputer.INSTANCE, true);
         addFunction(POINT3D_CONSTRUCTOR, APoint3DTypeComputer.INSTANCE, true);
@@ -1937,7 +1941,7 @@
         addPrivateFunction(REFERENCE_TILE, AInt32TypeComputer.INSTANCE, true);
         addPrivateFunction(GET_INTERSECTION, ARectangleTypeComputer.INSTANCE, 
true);

-        //geo functions
+        // geo functions
         addFunction(ST_AREA, ADoubleTypeComputer.INSTANCE, true);
         addFunction(ST_MAKE_POINT, AGeometryTypeComputer.INSTANCE, true);
         addFunction(ST_MAKE_POINT3D, AGeometryTypeComputer.INSTANCE, true);
@@ -2213,7 +2217,7 @@
     }

     static {
-        //  Aggregate functions
+        // Aggregate functions

         // AVG

@@ -3015,7 +3019,7 @@
     }

     public static BuiltinFunctionInfo resolveBuiltinFunction(String name, int 
arity) {
-        //TODO:optimize
+        // TODO:optimize
         BuiltinFunctionInfo finfo;
         finfo = getBuiltinFunctionInfo(FunctionConstants.newAsterix(name, 
arity));
         if (finfo != null) {
diff --git 
a/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/CurlDescriptor.java
 
b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/CurlDescriptor.java
new file mode 100644
index 0000000..64d0c84
--- /dev/null
+++ 
b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/CurlDescriptor.java
@@ -0,0 +1,333 @@
+/*
+ * 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 java.io.BufferedReader;
+import java.io.DataOutput;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.List;
+
+import org.apache.asterix.formats.nontagged.SerializerDeserializerProvider;
+import org.apache.asterix.om.base.AMissing;
+import org.apache.asterix.om.base.AString;
+import org.apache.asterix.om.functions.BuiltinFunctions;
+import org.apache.asterix.om.functions.IFunctionDescriptor;
+import org.apache.asterix.om.functions.IFunctionDescriptorFactory;
+import org.apache.asterix.om.pointables.ARecordVisitablePointable;
+import org.apache.asterix.om.pointables.base.DefaultOpenFieldType;
+import org.apache.asterix.om.pointables.base.IVisitablePointable;
+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.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
+import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluator;
+import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluatorFactory;
+import org.apache.hyracks.api.context.IEvaluatorContext;
+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.UTF8StringPointable;
+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;
+
+/**
+ * CURL function implementation for AsterixDB.
+ * Syntax: curl(url [, options])
+ * Options (record):
+ *   - request: HTTP method (GET, POST, PUT, DELETE, etc.)
+ *   - header: Header string (e.g., "Content-Type: application/json")
+ *   - data: Request body (string)
+ *   - user: Basic auth credentials ("username:password")
+ *   - connect-timeout: Connection timeout in milliseconds
+ *   - max-time: Max total time in milliseconds
+ */
+public class CurlDescriptor extends AbstractScalarFunctionDynamicDescriptor {
+    private static final long serialVersionUID = 1L;
+
+    public static final IFunctionDescriptorFactory FACTORY = new 
IFunctionDescriptorFactory() {
+        @Override
+        public IFunctionDescriptor createFunctionDescriptor() {
+            return new CurlDescriptor();
+        }
+    };
+
+    @Override
+    public FunctionIdentifier getIdentifier() {
+        return BuiltinFunctions.CURL;
+    }
+
+    @Override
+    public IScalarEvaluatorFactory createEvaluatorFactory(final 
IScalarEvaluatorFactory[] args) {
+        return new IScalarEvaluatorFactory() {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public IScalarEvaluator createScalarEvaluator(final 
IEvaluatorContext ctx) throws HyracksDataException {
+                return new CurlEvaluator(args, ctx);
+            }
+        };
+    }
+
+    private class CurlEvaluator implements IScalarEvaluator {
+        private final ArrayBackedValueStorage resultStorage = new 
ArrayBackedValueStorage();
+        private final DataOutput out = resultStorage.getDataOutput();
+        private final IScalarEvaluator urlEval;
+        private final IScalarEvaluator optionsEval;
+        private final IPointable urlArg = new VoidPointable();
+        private final IPointable optionsArg = new VoidPointable();
+        private final UTF8StringPointable stringPointable = new 
UTF8StringPointable();
+        private final ARecordVisitablePointable optionsRecord =
+                new 
ARecordVisitablePointable(DefaultOpenFieldType.NESTED_OPEN_RECORD_TYPE);
+
+        @SuppressWarnings("rawtypes")
+        private final ISerializerDeserializer stringSerde =
+                
SerializerDeserializerProvider.INSTANCE.getSerializerDeserializer(BuiltinType.ASTRING);
+        @SuppressWarnings("rawtypes")
+        private final ISerializerDeserializer missingSerde =
+                
SerializerDeserializerProvider.INSTANCE.getSerializerDeserializer(BuiltinType.AMISSING);
+
+        // Default configuration
+        private static final int DEFAULT_CONNECT_TIMEOUT = 5000;
+        private static final int DEFAULT_READ_TIMEOUT = 10000;
+
+        public CurlEvaluator(IScalarEvaluatorFactory[] args, IEvaluatorContext 
ctx) throws HyracksDataException {
+            this.urlEval = args[0].createScalarEvaluator(ctx);
+            if (args.length > 1) {
+                this.optionsEval = args[1].createScalarEvaluator(ctx);
+            } else {
+                this.optionsEval = null;
+            }
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public void evaluate(IFrameTupleReference tuple, IPointable result) 
throws HyracksDataException {
+            resultStorage.reset();
+            urlEval.evaluate(tuple, urlArg);
+
+            byte[] bytes = urlArg.getByteArray();
+            int offset = urlArg.getStartOffset();
+            int len = urlArg.getLength();
+
+            if (bytes[offset] != ATypeTag.SERIALIZED_STRING_TYPE_TAG) {
+                missingSerde.serialize(AMissing.MISSING, out);
+                result.set(resultStorage);
+                return;
+            }
+
+            stringPointable.set(bytes, offset + 1, len - 1);
+            String urlString = stringPointable.toString();
+
+            // Parse options - defaults
+            String method = "GET";
+            String data = null;
+            String user = null;
+            String header = null;
+            int connectTimeout = DEFAULT_CONNECT_TIMEOUT;
+            int readTimeout = DEFAULT_READ_TIMEOUT;
+
+            if (optionsEval != null) {
+                optionsEval.evaluate(tuple, optionsArg);
+                byte[] optBytes = optionsArg.getByteArray();
+                int optOffset = optionsArg.getStartOffset();
+
+                if (optBytes[optOffset] == 
ATypeTag.SERIALIZED_RECORD_TYPE_TAG) {
+                    try {
+                        optionsRecord.set(optionsArg);
+                        List<IVisitablePointable> fieldNames = 
optionsRecord.getFieldNames();
+                        List<IVisitablePointable> fieldValues = 
optionsRecord.getFieldValues();
+
+                        for (int i = 0; i < fieldNames.size(); i++) {
+                            String fieldName = 
extractString(fieldNames.get(i));
+                            if (fieldName == null) {
+                                continue;
+                            }
+
+                            IVisitablePointable fieldValue = 
fieldValues.get(i);
+                            String strValue = extractString(fieldValue);
+
+                            switch (fieldName) {
+                                case "request":
+                                    if (strValue != null) {
+                                        method = strValue;
+                                    }
+                                    break;
+                                case "data":
+                                    data = strValue;
+                                    break;
+                                case "user":
+                                    user = strValue;
+                                    break;
+                                case "header":
+                                    header = strValue;
+                                    break;
+                                case "connect-timeout":
+                                    connectTimeout = extractInt(fieldValue, 
DEFAULT_CONNECT_TIMEOUT);
+                                    break;
+                                case "max-time":
+                                    readTimeout = extractInt(fieldValue, 
DEFAULT_READ_TIMEOUT);
+                                    break;
+                            }
+                        }
+                    } catch (Exception e) {
+                        // If options parsing fails, continue with defaults
+                    }
+                }
+            }
+
+            try {
+                URL url = new URL(urlString);
+                HttpURLConnection con = (HttpURLConnection) 
url.openConnection();
+                con.setRequestMethod(method.toUpperCase());
+                con.setConnectTimeout(connectTimeout);
+                con.setReadTimeout(readTimeout);
+
+                // Set Basic Auth header if user is provided
+                if (user != null && !user.isEmpty()) {
+                    String encoded = 
Base64.getEncoder().encodeToString(user.getBytes(StandardCharsets.UTF_8));
+                    con.setRequestProperty("Authorization", "Basic " + 
encoded);
+                }
+
+                // Set custom header
+                if (header != null && !header.isEmpty()) {
+                    int colonIdx = header.indexOf(':');
+                    if (colonIdx > 0) {
+                        String headerName = header.substring(0, 
colonIdx).trim();
+                        String headerValue = header.substring(colonIdx + 
1).trim();
+                        con.setRequestProperty(headerName, headerValue);
+                    }
+                }
+
+                // Send data for POST/PUT
+                if (data != null && !data.isEmpty()) {
+                    con.setDoOutput(true);
+                    try (OutputStream os = con.getOutputStream()) {
+                        byte[] input = data.getBytes(StandardCharsets.UTF_8);
+                        os.write(input, 0, input.length);
+                    }
+                }
+
+                int status = con.getResponseCode();
+
+                // Read response
+                InputStream is = (status >= 200 && status < 300) ? 
con.getInputStream() : con.getErrorStream();
+                if (is == null) {
+                    is = con.getInputStream();
+                }
+
+                StringBuilder content = new StringBuilder();
+                if (is != null) {
+                    try (BufferedReader in = new BufferedReader(new 
InputStreamReader(is, StandardCharsets.UTF_8))) {
+                        String inputLine;
+                        while ((inputLine = in.readLine()) != null) {
+                            content.append(inputLine);
+                        }
+                    }
+                }
+                con.disconnect();
+
+                stringSerde.serialize(new AString(content.toString()), out);
+
+            } catch (Exception e) {
+                missingSerde.serialize(AMissing.MISSING, out);
+            }
+
+            result.set(resultStorage);
+        }
+
+        /**
+         * Extract a UTF8 string from an IVisitablePointable.
+         * The pointable contains a type tag byte followed by UTF8 string data.
+         */
+        private String extractString(IVisitablePointable pointable) {
+            byte[] bytes = pointable.getByteArray();
+            int offset = pointable.getStartOffset();
+            int len = pointable.getLength();
+
+            if (len <= 0) {
+                return null;
+            }
+
+            // Check if it's a string type
+            byte typeTag = bytes[offset];
+            if (typeTag == ATypeTag.SERIALIZED_STRING_TYPE_TAG) {
+                // Skip type tag, parse UTF8 string
+                if (len > 1) {
+                    UTF8StringPointable sp = new UTF8StringPointable();
+                    sp.set(bytes, offset + 1, len - 1);
+                    return sp.toString();
+                }
+                return "";
+            }
+
+            // For field names, they don't have a type tag - they're raw UTF8
+            // Check if first byte looks like UTF8 length marker
+            int firstByte = bytes[offset] & 0xFF;
+            if (firstByte < 128 || (firstByte >= 0xC0 && firstByte <= 0xFD)) {
+                // Looks like UTF8 string without type tag
+                UTF8StringPointable sp = new UTF8StringPointable();
+                sp.set(bytes, offset, len);
+                return sp.toString();
+            }
+
+            return null;
+        }
+
+        /**
+         * Extract an integer from an IVisitablePointable.
+         */
+        private int extractInt(IVisitablePointable pointable, int 
defaultValue) {
+            byte[] bytes = pointable.getByteArray();
+            int offset = pointable.getStartOffset();
+            int len = pointable.getLength();
+
+            if (len <= 0) {
+                return defaultValue;
+            }
+
+            byte typeTag = bytes[offset];
+            try {
+                if (typeTag == ATypeTag.SERIALIZED_INT32_TYPE_TAG && len >= 5) 
{
+                    return ((bytes[offset + 1] & 0xFF) << 24) | ((bytes[offset 
+ 2] & 0xFF) << 16)
+                            | ((bytes[offset + 3] & 0xFF) << 8) | 
(bytes[offset + 4] & 0xFF);
+                } else if (typeTag == ATypeTag.SERIALIZED_INT64_TYPE_TAG && 
len >= 9) {
+                    long val = 0;
+                    for (int i = 0; i < 8; i++) {
+                        val = (val << 8) | (bytes[offset + 1 + i] & 0xFF);
+                    }
+                    return (int) val;
+                } else if (typeTag == ATypeTag.SERIALIZED_INT8_TYPE_TAG && len 
>= 2) {
+                    return bytes[offset + 1];
+                } else if (typeTag == ATypeTag.SERIALIZED_INT16_TYPE_TAG && 
len >= 3) {
+                    return ((bytes[offset + 1] & 0xFF) << 8) | (bytes[offset + 
2] & 0xFF);
+                }
+            } catch (Exception e) {
+                // Fall through to default
+            }
+            return defaultValue;
+        }
+    }
+}
diff --git 
a/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/functions/FunctionCollection.java
 
b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/functions/FunctionCollection.java
index 106b165..cd21b6d 100644
--- 
a/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/functions/FunctionCollection.java
+++ 
b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/functions/FunctionCollection.java
@@ -382,6 +382,7 @@
 import 
org.apache.asterix.runtime.evaluators.functions.CreateQueryUIDDescriptor;
 import 
org.apache.asterix.runtime.evaluators.functions.CreateRectangleDescriptor;
 import org.apache.asterix.runtime.evaluators.functions.CreateUUIDDescriptor;
+import org.apache.asterix.runtime.evaluators.functions.CurlDescriptor;
 import 
org.apache.asterix.runtime.evaluators.functions.DecodeDataverseNameDescriptor;
 import org.apache.asterix.runtime.evaluators.functions.DeepEqualityDescriptor;
 import org.apache.asterix.runtime.evaluators.functions.FromBaseDescriptor;
@@ -1390,6 +1391,9 @@
         fc.add(CheckListDescriptor.FACTORY);
         fc.add(CheckIntegerDescriptor.FACTORY);

+        // Network functions
+        fc.add(CurlDescriptor.FACTORY);
+
         
ServiceLoader.load(IFunctionRegistrant.class).iterator().forEachRemaining(c -> 
c.register(fc));
         return fc;
     }

--
To view, visit https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/20800?usp=email
To unsubscribe, or for help writing mail filters, visit 
https://asterix-gerrit.ics.uci.edu/settings?usp=email

Gerrit-MessageType: newchange
Gerrit-Project: asterixdb
Gerrit-Branch: master
Gerrit-Change-Id: I346e01f1e05a2bb6ce7aef2c2f6393844eed3b2f
Gerrit-Change-Number: 20800
Gerrit-PatchSet: 1
Gerrit-Owner: Peeyush Gupta <[email protected]>

Reply via email to