How do I use a parameter value, set by a user in a web application, in an Optic API query in Marklogic?

186 Views Asked by At

Blockquote

I have a web application (written in js) where a user can enter a name (along with other criteria) and a search against a ML database is performed and a tabular representation of some of the data associated to that name (or other search criteria) is returned. In the first stages of development I wrote optic api queries with the value for name hard coded in the where statement of the query. I did this so I could test that that the REST api was calling the query properly, etc. That all is working and I can see the right results for that hard coded name being returned in the web app. Now I want to pass the user supplied value for name to the Optic api query (EntityInformation_Optic_API_Query.json) to replace the hard coded value in where statement. How do I do this? Thanks in advance.


owner edit

The application is written in PHP and I have copied code below. In this you can see where the optic api query is called and where the parameters are built.

<?php
if (!defined('RSS_BASE_URL')) exit;

use MarkLogic\MLPHP as MLPHP;

function rss_api_call($json, $params = array(), $resource = 'rows', $verb = 'POST')
{
    $json = __DIR__ . '/json/' . $json;

    if (!file_exists($json)) return false;

    $request = new MLPHP\RESTRequest('POST', 'rows', $params, file_get_contents($json), array('Content-type' => 'application/json'));
    $response = rss_get_client()->send($request);

    return json_decode($response->getBody());
}

function rss_api_entities()
{
    $params = array();

    return rss_api_call('EntityInformation_Optic_API_Query.json', $params);
    //return (empty($params)) ? '' : rss_api_call('EntityInformation_Optic_API_Query.json', $params);
}

function rss_api_search()
{
    $params = array();

    if (isset($_POST['companyname']) && !empty($_POST['companyname']))
    {
        $params['CompanyName'] = htmlspecialchars($_POST['companyname']);
    }

    if (isset($_POST['EIN']) && !empty($_POST['ein']))
    {
        $params['EIN'] = htmlspecialchars($_POST['ein']);
    }

    if (isset($_POST['city']) && !empty($_POST['city']))
    {
        $params['EntityCity'] = htmlspecialchars($_POST['city']);
    }

    if (isset($_POST['state']) && !empty($_POST['state']))
    {
        $params['EntityState'] = htmlspecialchars($_POST['state']);
    }

    if (isset($_POST['zip']) && !empty($_POST['zip']))
    {
        $params['EntityZip'] = htmlspecialchars($_POST['zip']);
    }

    return rss_api_call('SearchResults_Optic_API_Query.json', $params);
    //return (empty($params)) ? '' : rss_api_call('SearchResults_Optic_API_Query.json', $params);
}

function rss_asset_path($path, $file_name)
{
    global $rss_manifest;

    if (empty($rss_manifest))
    {
        ob_start();

        include(__DIR__ . '/../manifest.json');

        $rss_manifest = json_decode(ob_get_clean(), true);
    }

    if (isset($rss_manifest[$file_name]))
    {
        $file_name = $rss_manifest[$file_name];
    }

    return RSS_ASSET_PATH . $path . '/' . $file_name;
}

function rss_box($title, $content)
{
    echo '<div class="rss-box">' .
        '<div class="rss-box-title">' . $title . '</div>' .
        '<div class="rss-box-content">' . $content . '</div>' .
        '</div>';
}

function rss_get_client()
{
    $mlphp = new MLPHP\MLPHP(array
    (
        'host' => RSS_API_HOST,
        'port' => RSS_API_PORT,
        'version' => RSS_API_VERSION,
        'username' => RSS_API_USERNAME,
        'password' => RSS_API_PASSWORD
    ));

    return $mlphp->getClient();
}

function rss_hidden_search_fields()
{
    echo '<div class="rss-hidden">';

    $fields = array('debug', 'companyname', 'ein', 'city', 'state', 'zip');

    foreach ($fields as $field)
    {
        echo (isset($_POST[$field])) ? '<input name="' . $field . '" type="hidden" value="' . htmlspecialchars($_POST[$field]) . '" />' : '';
    }

    if (!empty($_POST['social']))
    {
        $social = (is_array($_POST['social'])) ? $_POST['social'] : array($_POST['social']);

        foreach ($social as $social_network)
        {
            echo '<input name="social[]" type="hidden" value="' . htmlspecialchars($social_network) . '" />';
        }
    }

    echo (empty($_POST['social-select-all'])) ? '' : '<input name="social-select-all" type="hidden" value="1" />';
    echo '</div>';
}



Part of the EntityInformation_Optic_API_Query.json is below
-----

edit (information from a comment below):

The request will be a POST to /v1/rows. Does the following payload look correct? 

```javascript
{
    "$optic": {
        "ns": "op",
        "fn": "operators",
        "args": [
            {
                "ns": "op",
                "fn": "from-view",
                "args": [ "TestSchema", "SUT", null, null ]
            },
            {
                "ns": "op",
                "fn": "where",
                "args": [
                    {
                        "ns": "op",
                        "fn": "eq",
                        "args": [
                            {
                                "ns": "op",
                                "fn": "col",
                                "args": [ "CompanyName" ]
                            },
                            "${req.params.CompanyName}"
                        ]
                    }
                ]
            }
3

There are 3 best solutions below

2
On

It depends on how you're setting up your application.

REST API

If your code is going to be a REST API extension, the function you write will have a params parameter. For JavaScript, this will be an object. See The JavaScript Resource Extension Interface. From that part of the docs:

If the request is PUT /v1/resource/my-ext?rs:p=1&rs:p=2, then the value of params.p is ["1", "2"].

Main Module

If the client will access your code through a regular (non-REST) main module, then use the xdmp.getRequestField.


Edit: I updated your question with some more information from you comment. It looks to me like you've got a middle tier that is taking parameters from the client (browser) and using them to build a query to pass to /v1/rows. It looks like "${req.params.CompanyName}" is intended to be interpolated so that your middle tier gets the value of req.params.CompanyName, which would then be put into the string that gets sent to MarkLogic.

Is it possible that the "${req.params.CompanyName}" got added to a string with the quotes intact, so that the middle tier can't do the interpolation? If you can provide more information about how the payload to /v1/rows is being built, we might be able to offer more help.

0
On

For a Java or Node.js web application, the best approach might be to write a function that takes the value and builds and returns an Optic query.

In other environments, using a JSON representation and inserting the value would be safest.

Assembling the JSON serialization of the Optic query as a string is possible with escaping though sometimes hard to debug. On the MarkLogic enode, the JSON is parsed as JSON instead of evaluated, so there isn't a risk of injection attacks, but parse failures are possible.

Hoping that helps,

3
On

I believe you need to use "bind" parameters. So your plan would look like this:

{
  "$optic": {
    "ns": "op",
    "fn": "operators",
    "args": [
      {
        "ns": "op",
        "fn": "from-view",
        "args": [
          "TestSchema",
          "SUT",
          null,
          null
        ]
      },
      {
        "ns": "op",
        "fn": "where",
        "args": [
          {
            "ns": "op",
            "fn": "eq",
            "args": [
              {
                "ns": "op",
                "fn": "col",
                "args": [
                  "CompanyName"
                ]
              },
              {
                "ns": "op",
                "fn": "param",
                "args": [
                  "companyName"
                ]
              }
            ]
          }
        ]
      }
    ]
  }
}

And then when you post that to /v1/rows, you need to set the companyName bind parameter and type. So, add the follow parameters to your POST:

bind:companyName=${req.params.CompanyName}
bind:companyName:type=xs:string

Assuming ${req.params.CompanyName} contains the company name value passed by the user.

As a side note, dealing with serialized Optic plans by hand can tough. I would recommend you put this behind a REST extension and just pass in the parameters. If you really want to keep the Optic plans in your middle tier, you can always use MarkLogic's Query Console to generate the plan with the proper op.param() calls where you want to have parameterized values. I.E. something like:

const op = require('/MarkLogic/optic');
const plan = op
  .fromView("TestSchema", "SUT")
  .where(
    op.eq(
      op.col("CompanyName"), op.param("companyName")
    )
  )

plan.export()