surrealdb / surrealdb.node

A Node.js engine for the SurrealDB JavaScript SDK
https://surrealdb.com
Apache License 2.0
61 stars 10 forks source link

Unable to make record links #12

Closed MatthewAry closed 11 months ago

MatthewAry commented 12 months ago

When I attempt to make a record link:

    let customerOrganization = null;
    let customerUser1 = null;

    customerOrganization = await db.create('organization', {
      name: 'Customer organization',
      type: ['customer']
    });
    console.log('created customer', customerOrganization)
    // created customer [
    //   {
    //     id: 'organization:id1az0rct82w9gw0vd7t',
    //     name: 'Customer organization',
    //     type: [ 'customer' ],
    //     updatedAt: '2023-09-23T16:24:32.941479Z'
    //   }
    // ]
    customerUser1 = await db.query(`
      INSERT INTO userAccount {
        firstName: $firstName,
        lastName: $lastName,
        email: $email,
        organization: $organization
      };
    `, {
      firstName: 'Customer',
      lastName: 'User1',
      email: 'test1@test.com',
      organization: customerOrganization[0].id
    })
    // Found 'organization:rxzhzkxhx7c0bmx6j2x8' for field `organization`, with record `userAccount:ki86v3aqoe8h9yvdq6o3`, but expected a record<organization>
    console.log('created user', customerUser1)

It doesn't work because it treats the ID as a string and not an actual record. This might be resolved by using string::split combined with type::thing but we shouldn't have to do that.

Because this is using the rust driver under the hood, its probably not handling the type in a way that works well for JS based platforms.

MatthewAry commented 12 months ago

This doesn't work either

customerUser1 = await db.query(`
      INSERT INTO userAccount {
        firstName: $firstName,
        lastName: $lastName,
        email: $email,
        organization: <record<organization>> $organization
      };
    `, {
      firstName: 'Customer',
      lastName: 'User1',
      email: 'test1@test.com',
      organization: customerOrganization[0].id
    })
Error: There was a problem with the database: Found '<record<organization>> organization:66x760hhwmdrmndb89cm' for field `organization`, with record `userAccount:lys6kcc0esibihqdeiwh`, but expected a record<organization>
kearfy commented 12 months ago

To add to the above comment, this nicely showcases that the passed values are not being computed there

sebastianwessel commented 11 months ago

Didn't try it, but maybe type::thing can help to work around this. Something like this:

customerUser1 = await db.query(`
      INSERT INTO userAccount {
        firstName: $firstName,
        lastName: $lastName,
        email: $email,
        organization: type::thing('organization',$organization)
      };
    `, {
      firstName: 'Customer',
      lastName: 'User1',
      email: 'test1@test.com',
      organization: customerOrganization[0].id
    })
MatthewAry commented 11 months ago

@sebastianwessel If I recall correctly, it was tried. See this thread on discord. I'm not really sure what the problem here is. Shouldn't the resolved variables be interpolated into the query string? I think there's something different happening here.

kearfy commented 11 months ago

@rushmorem this seems to also be an issue in the WASM library. Consider the following example:

const db = new Surreal();
await db.connect('memory');
await db.use({ ns: 'test', db: 'test' });

await Promise.all([
    db.query("type::is::record($test)", { test: 'a:1' }).then(r => r[0]),
    db.query("type::is::string($test)", { test: 'a:1' }).then(r => r[0]),
    db.query("type::is::record(type::thing($test))", { test: 'a:1' }).then(r => r[0]),
    db.query("type::is::record(<record> $test)", { test: 'a:1' }).catch(e => e)
]);

// Results in:
[false, true, false, "Expected a record but cannot convert 'a:1' into a record"]

This works fine in a normal SQL repl however:

test/test> type::is::record(type::thing('a:1'))
[
        true
]

Do you have any ideas?

rushmorem commented 11 months ago

Thanks @kearfy. I will take a look. I think we can fix this from the query layer.

MatthewAry commented 11 months ago

In the meantime, the only way to make this work is to interpolate the string directly into a query. I made a regex to hopefully prevent injection attacks, but really my hope is that we can get this fixed and that the SDK will prevent injection attacks on the data or bindings we pass in. No promises that this Regex gives full protection.

/**
 * SurrealDB Record ID Validation Regex
 * 
 * This regex pattern is designed to validate the different ID formats used in SurrealDB's SurrealQL language.
 * 
 * Supported Formats:
 * 1. Text Record IDs: Combinations of letters, numbers, and underscores. Example: `company:acusa`
 * 2. Complex character IDs: Surrounded by backticks ` or by the characters ⟨ and ⟩. Example: `article:⟨uuid-format⟩`
 * 3. Numeric Record IDs: 64-bit integers. Example: `temperature:1234567890`
 * 4. Built-in ID generation functions: Such as `rand()`, `ulid()`, and `uuid()`.
 * 
 * Note: Deeply nested objects and arrays are not supported by this regex.
 * It is advisable to use a parser for handling deeply nested structures.
 * 
 * @example
 * const recordID = "company:acusa";
 * const isValid = regexPattern.test(recordID);
 */

const regexPattern = /^([\w:]+|`[\w:-]+`|⟨[\w:-]+⟩|\d+|{[^}]+}|\[.*\]|(rand|ulid|uuid)\(\))$/;
MatthewAry commented 11 months ago

It's possible that this also affects other types like datetime fields. I think I started seeing errors with that in my tests yesterday.