Skip to content

Support for expressions #101

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 27 commits into from
Closed

Support for expressions #101

wants to merge 27 commits into from

Conversation

dorantor
Copy link
Contributor

How it's used in our project(simplified):

    $uuid = '0f372656-6a5b-4727-a4c4-f6357775d926';
    $uuidExpr = $chc->expr("UUIDStringToNum('{$uuid}')");
    $stat = $chc->insert(
        'sometable',
        ['2018-10-18', $uuidExpr],
        ['created_date', 'event_id',]
    );

Where simplified sometable is:

CREATE TABLE sometable (
    created_date Date,
    event_id FixedString(16)
) ENGINE = MergeTree(
    created_date,
    event_id,
    (created_date, event_id),
    8192
);

/**
* @var string
*/
protected $expression = '';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO there should not be a default value and not be protected. Let there only be a constructor as the way to create a valid object.

Suggested change
protected $expression = '';
private $expression;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Sorry, just now understood that this change could be applied right in github.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Np, new feature introduced yesterday ;)

namespace ClickHouseDB\Query;

/**
* Query expression
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would deserve more explanation

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated docblock with example and param type.

/**
* Query expression
*
* @package ClickHouseDB
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment can be removed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.

Copy link
Contributor

@simPod simPod left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I like the idea 👍

Can we cover it with integration test so we make sure it doesn't get broken in the future? I suggest to place it into new ClickHouseDB\Tests\Query\ExpressionTest

@simPod
Copy link
Contributor

simPod commented Oct 17, 2018

Travis Code Style build is currently broken (will be fixed when #100 is merged) so it can be ignored

Scrutinizer however found some issues, mostly the code style https://scrutinizer-ci.com/g/smi2/phpClickHouse/inspections/e96a39a6-5929-44aa-ae63-943b77da290d/issues/

@dorantor
Copy link
Contributor Author

Scrutinizer is now ok. I'll remove method expr() from client, but, as I mentioned, still think Expression creation should be encapsulated by library. But I'm not familiar enough with the library to make a good suggestion how and where it should be done.

@simPod
Copy link
Contributor

simPod commented Oct 18, 2018

Everything that is public = API. And even though you create another method that instantiates it, it will still be perfectly valid to instantiate it on it's own. If you want to encapsulate it, you can create something like expression builder (example https://www.doctrine-project.org/projects/doctrine-collections/en/latest/expression-builder.html).

Copy link
Contributor

@simPod simPod left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And what do you think about the test 🤔 (#101 (review))?

@dorantor
Copy link
Contributor Author

Tests are a must. Just wanted to finalize how it will be used first. Regarding ExpressionBuilder - most probably I will not be able to do it in any close future :)

@dorantor
Copy link
Contributor Author

In older version there was no call $value = ValueFormatter::formatValue($value, false); in src/Quote/StrictQuoteLine.php, so my solution was working as expected. Now it's there and converts Expression to string and applies quoting(which I try to avoid with Expression class). Currently I see two options how to make it work again:

  • Make Expression implementation of Type interface and return $this in getValue()
  • Add check in ValueFormatter::formatValue for Expression

But both options doesn't look good to me.

@dorantor
Copy link
Contributor Author

dorantor commented Oct 18, 2018

Commit that introduced ValueFormatter call says

Refactor formatting

But it doesn't gives any idea why type conversion was added. W/o knowing reasons for that I cannot offer a good solution.

@simPod
Copy link
Contributor

simPod commented Oct 18, 2018

Yup, that cleaned up value formatting code that was spread across several classes into one

You can replicate this part for Expression and should be good 5f4fe72#diff-8b1652bb2da78b48114bdfa44b1f9b3fR35

@dorantor
Copy link
Contributor Author

Yup, that cleaned up value formatting code that was spread across several classes into one

You can replicate this part for Expression and should be good 5f4fe72#diff-8b1652bb2da78b48114bdfa44b1f9b3fR35

Not sure I understood correctly. You mean make it this way?

if ($value instanceof Expression) {

@simPod
Copy link
Contributor

simPod commented Oct 18, 2018

Not sure I understood correctly. You mean make it this way?

Exactly. And maybe remove __toString() and replace with get(), getValue() or something similar

@dorantor
Copy link
Contributor Author

Not sure I understood correctly. You mean make it this way?

Exactly. And maybe remove __toString() and replace with get(), getValue() or something similar

Main reasoning for class wrapper (Expression) is to bypass check in src/Quote/StrictQuoteLine.php:

if (is_string($value) && $encode) {

If replace __toString() with getValue():string and use it inValueFormatter::formatValue() as:

        if ($value instanceof Expression) {
            return $value->getValue();
        }

check I noted before will not be bypassed.

@dorantor
Copy link
Contributor Author

This is what I can offer for now, but I don't like this solution, to be honest.

@simPod
Copy link
Contributor

simPod commented Oct 18, 2018

What in particular you don't like about it?

class Expression
{
/** @var string */
private $expression;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more thing is on my mind. This Expression you introduced could easily replace my Type I introduced here #94.

If $expression would be of type mixed and we dropped __toString() and replaced with something like getValue() (@return mixed) I could then drop those classes of mine in next PR.

My intention was to use it like this

UInt64::fromString('18446744073709551615')

but now it could be replaced with

new Expression('18446744073709551615')

for lower numbers eg. new Expression(1844674)

My main point is, why to limit the expression only for strings?

Copy link
Contributor Author

@dorantor dorantor Oct 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was different intention and reasoning for Type and Expression.
Expression was made to bypass quoting. Inspired by other DB-related libs. But, btw, not necessarily in a best way to implement this intention. I just found it less obtrusive and easier to merge back to my fork in future.
Type, as I can see, introduced for different reason - to allow safely and in a controlled fashion add new, potentially complex types(what about Date type, f.ex.?).

While Expression seems to be universal here I'm against replacing Type with Expression, because Expression is a last resort. Unsafe one, I must say. Should be used only when there are no other options. In theory, with time, Expression could be replaced with some support for functions from library itself. Like
new Function\UUIDStringToNum($uuid)
instead of
new Expression("UUIDStringToNum('0f372656-6a5b-4727-a4c4-f6357775d926')").

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kk, let's just leave it like it is

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, maybe, Expression could become either a higher level (abstract?) class or even Interface and Type can be a descendant to it. Because you're right in one aspect - Expression could solve a wider range of requirements including ones that are solved by Type.
But it will require to refactor this part in src/Quote/StrictQuoteLine.php:

            $value = ValueFormatter::formatValue($value, false);

            if (is_float($value) || is_int($value)) {
                return (string) $value;
            }

            if (is_string($value) && $encode) {

if you want Expression::getValue() to return string. Because otherwise string
"UUIDStringToNum('0f372656-6a5b-4727-a4c4-f6357775d926')"
will become
'\'UUIDStringToNum(\\\'0f372656-6a5b-4727-a4c4-f6357775d926\\\')\''
after quoting.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right it might need some tweak in that part of code. But see this line

https://github.com/smi2/phpClickHouse/blob/master/src/Quote/StrictQuoteLine.php#L74

If replaced by if ($value instanceof Expression) { it might do the trick.

BTW I'd definitely be for interface Expression rather than abstract class Expression

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, missed that one. So it should be something like this IMO:

            if ($value instanceof NumericType) {
                $encode = $value->doQuoting();
            }

But in this case abstract class AND interface required. Abstract is required to define default doQuoting() : boolean. I think I can do it after this one is merged. Because, obviously, it will be refactoring of Type and Expression.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I'd name it something like needsEncoding() : bool and abstract class is IMO not required, we don't really need default value so interface will do. But for sure in next PR :)

@dorantor
Copy link
Contributor Author

What in particular you don't like about it?

Cannot say. It just smells bad for me. Something is wrong here.
Validating against class instead of interface?

But it's working, so I'm not freaking out :)

@simPod
Copy link
Contributor

simPod commented Oct 18, 2018

I suppose some interface will emerge soon for that case. Type and Expression could have shared one.

Copy link
Contributor

@simPod simPod left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🚢

WDYT @isublimity?

@isublimity
Copy link
Contributor

Merge to master
ed7ae42

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants