Skip to content
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

protocol: support query attribute since mysql 8.0.23 #55175

Open
wants to merge 12 commits into
base: master
Choose a base branch
from

Conversation

kafka1991
Copy link
Contributor

@kafka1991 kafka1991 commented Aug 4, 2024

What problem does this PR solve?

Issue Number: close #55174

Problem Summary:

  1. Supports parse query attributes in protocol layer
  2. support mysql_query_attribute_string builtin function.

What changed and how does it work?

Check List

Tests

  • Unit test
  • Integration test
  • Manual test (add detailed scripts or steps below)
  • No need to test
    • I checked and no code files have been changed.

Side effects

  • Performance regression: Consumes more CPU
  • Performance regression: Consumes more Memory
  • Breaking backward compatibility

Documentation

  • Affects user behaviors
  • Contains syntax changes
  • Contains variable changes
  • Contains experimental features
  • Changes MySQL compatibility

Release note

Please refer to Release Notes Language Style Guide to write a quality release note.

None

@ti-chi-bot ti-chi-bot bot added do-not-merge/invalid-title do-not-merge/needs-tests-checked do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. release-note-none Denotes a PR that doesn't merit a release note. labels Aug 4, 2024
@sre-bot
Copy link
Contributor

sre-bot commented Aug 4, 2024

CLA assistant check
All committers have signed the CLA.

@ti-chi-bot ti-chi-bot bot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Aug 4, 2024
Copy link

ti-chi-bot bot commented Aug 4, 2024

Welcome @kafka1991!

It looks like this is your first PR to pingcap/tidb 🎉.

I'm the bot to help you request reviewers, add labels and more, See available commands.

We want to make sure your contribution gets all the attention it needs!



Thank you, and welcome to pingcap/tidb. 😃

Copy link

ti-chi-bot bot commented Aug 4, 2024

Hi @kafka1991. Thanks for your PR.

I'm waiting for a pingcap member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

@ti-chi-bot ti-chi-bot bot added size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. needs-ok-to-test Indicates a PR created by contributors and need ORG member send '/ok-to-test' to start testing. labels Aug 4, 2024
Copy link

tiprow bot commented Aug 4, 2024

Hi @kafka1991. Thanks for your PR.

PRs from untrusted users cannot be marked as trusted with /ok-to-test in this repo meaning untrusted PR authors can never trigger tests themselves. Collaborators can still trigger tests on the PR using /test all.

I understand the commands that are listed here.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@lance6716
Copy link
Contributor

/ok-to-test

@ti-chi-bot ti-chi-bot bot added ok-to-test Indicates a PR is ready to be tested. and removed needs-ok-to-test Indicates a PR created by contributors and need ORG member send '/ok-to-test' to start testing. labels Aug 4, 2024
Copy link

codecov bot commented Aug 5, 2024

Codecov Report

Attention: Patch coverage is 5.79710% with 130 lines in your changes missing coverage. Please review.

Project coverage is 73.3542%. Comparing base (1af4fbe) to head (19f2ec8).
Report is 8 commits behind head on master.

Additional details and impacted files
@@               Coverage Diff                @@
##             master     #55175        +/-   ##
================================================
+ Coverage   72.9842%   73.3542%   +0.3700%     
================================================
  Files          1697       1697                
  Lines        468948     469903       +955     
================================================
+ Hits         342258     344694      +2436     
+ Misses       105590     104121      -1469     
+ Partials      21100      21088        -12     
Flag Coverage Δ
integration 42.7723% <5.7971%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
dumpling 52.6910% <ø> (ø)
parser ∅ <ø> (∅)
br 45.0853% <ø> (+0.0084%) ⬆️

@kafka1991
Copy link
Contributor Author

i don't have much background about bazel, can anyone help me with bazel compilation error ?

@lance6716
Copy link
Contributor

lance6716 commented Aug 6, 2024

i don't have much background about bazel, can anyone help me with bazel compilation error ?

For the errors of https://do.pingcap.net/jenkins/blue/organizations/jenkins/pingcap%2Ftidb%2Fghpr_check/detail/ghpr_check/14289/pipeline

linting
  ✘  https://revive.run/r#exported  exported var ErrUnknownFieldType should have comment or be unexported  
  pkg/param/binary_params.go:22:5

please check this line and fix it. It means public (exported) variables should have comment which is expected at the place 1 line above the definition.

Also, please sign the CLA in the first comment.

@kafka1991
Copy link
Contributor Author

kafka1991 commented Aug 6, 2024

i don't have much background about bazel, can anyone help me with bazel compilation error ?

For the errors of https://do.pingcap.net/jenkins/blue/organizations/jenkins/pingcap%2Ftidb%2Fghpr_check/detail/ghpr_check/14289/pipeline

linting
  ✘  https://revive.run/r#exported  exported var ErrUnknownFieldType should have comment or be unexported  
  pkg/param/binary_params.go:22:5

please check this line and fix it. It means public (exported) variables should have comment which is expected at the place 1 line above the definition.

Thanks for your help, I have solved the problem.

Also, please sign the CLA in the first comment.

The CLA had been signed.

@lance6716
Copy link
Contributor

vicgao seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.

Maybe your GitHub account email is not the same as the email in this PR's commit?

@kafka1991
Copy link
Contributor Author

vicgao seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.

Maybe your GitHub account email is not the same as the email in this PR's commit?

Had add the email in this PR's commit to github accounts in setting.

@lance6716
Copy link
Contributor

Files errors.toml and /tmp/errors.toml.before differ
make: *** [errdoc] Error 1

you can use make errdoc and then commit the changed files

@YangKeao
Copy link
Member

YangKeao commented Aug 8, 2024

The failed pull-mysql-client-test is the test from https://github.com/mysql/mysql-server/tree/trunk/testclients. We only run a subset of the tests in CI (because many of these tests are known to fail), so you can focus on the failed one in CI test_bug5194. It seems that the params protocol implementation has some bugs.

If you need any help to reproduce the test result or debug the protocol related codes, feel free to @ me here.

@kafka1991
Copy link
Contributor Author

The failed pull-mysql-client-test is the test from https://github.com/mysql/mysql-server/tree/trunk/testclients. We only run a subset of the tests in CI (because many of these tests are known to fail), so you can focus on the failed one in CI test_bug5194. It seems that the params protocol implementation has some bugs.

If you need any help to reproduce the test result or debug the protocol related codes, feel free to @ me here.

Thanks for your remind, i will try to reproduce it by myself.

@ti-chi-bot ti-chi-bot bot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Aug 10, 2024
@kafka1991
Copy link
Contributor Author

The failed pull-mysql-client-test is the test from https://github.com/mysql/mysql-server/tree/trunk/testclients. We only run a subset of the tests in CI (because many of these tests are known to fail), so you can focus on the failed one in CI test_bug5194. It seems that the params protocol implementation has some bugs.

If you need any help to reproduce the test result or debug the protocol related codes, feel free to @ me here.

You are right, params protocol implementation for execute statement did has some bugs and they had been solved.

By the way, server has a minor compatibility issue when tested by higher version of mysql_client_test:

All lowercase letters in 'innodb'
https://github.com/mysql/mysql-server/blob/trunk/testclients/mysql_client_fw.cc

image

Not all lowercase letters in 'innodb'
https://github.com/pingcap/tidb/blob/master/pkg/executor/infoschema_reader.go

image

@kafka1991 kafka1991 changed the title [WIP] support query attribute since mysql 8.0.23 support query attribute since mysql 8.0.23 Aug 10, 2024
@ti-chi-bot ti-chi-bot bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Aug 10, 2024
@YangKeao
Copy link
Member

By the way, server has a minor compatibility issue when tested by higher version of mysql_client_test:

MySQL also shows InnoDB:

+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| ENGINE             | SUPPORT | COMMENT                                                        | TRANSACTIONS | XA   | SAVEPOINTS |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| FEDERATED          | NO      | Federated MySQL storage engine                                 | NULL         | NULL | NULL       |
| MEMORY             | YES     | Hash based, stored in memory, useful for temporary tables      | NO           | NO   | NO         |
| InnoDB             | DEFAULT | Supports transactions, row-level locking, and foreign keys     | YES          | YES  | YES        |
| PERFORMANCE_SCHEMA | YES     | Performance Schema                                             | NO           | NO   | NO         |
| MyISAM             | YES     | MyISAM storage engine                                          | NO           | NO   | NO         |
| MRG_MYISAM         | YES     | Collection of identical MyISAM tables                          | NO           | NO   | NO         |
| BLACKHOLE          | YES     | /dev/null storage engine (anything you write to it disappears) | NO           | NO   | NO         |
| CSV                | YES     | CSV storage engine                                             | NO           | NO   | NO         |
| ARCHIVE            | YES     | Archive storage engine                                         | NO           | NO   | NO         |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+

I think the result of the query select * from information_schema.engines where engine = 'innodb' differs because of the collation. It seems that MySQL is using a case insensitive collation for information_schema tables, but TiDB is using utf8mb4_bin. I remembered it's a known issue but I didn't find it now 🤦.

@YangKeao YangKeao changed the title support query attribute since mysql 8.0.23 protocol: support query attribute since mysql 8.0.23 Aug 12, 2024
@dveeden dveeden requested a review from lance6716 February 24, 2025 12:56
@kafka1991
Copy link
Contributor Author

From the CI build job:

pkg/server/conn_stmt_params_test.go:322:93: param min has same name as predeclared identifier (predeclared)

Maybe change min to _min or minute ?

Fixed it

NextVal = "nextval"
LastVal = "lastval"
SetVal = "setval"
QueryAttrString = "mysql_query_attribute_string"
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
QueryAttrString = "mysql_query_attribute_string"
// TiDB Query
QueryAttrString = "mysql_query_attribute_string"

@kafka1991
Copy link
Contributor Author

#!/bin/python3
import mysql.connector

c = mysql.connector.connect(
    host="127.0.0.1", port=4000, user="root", database="test", ssl_disabled=True
)

cur = c.cursor(prepared=True)
cur.add_attribute("testattr", "test value")

cur.execute("SELECT mysql_query_attribute_string(%s)", ("testattr",))
for row in cur:
    print(row)

print(cur.get_attributes())

cur.clear_attributes()
print(cur.get_attributes())
cur.close()
c.close()

This script succeeds against MySQL 9.1.0 but fails against TiDB (master_query_attr branch).

dvaneeden@dve-carbon:~$ ./query_attributes.py 
Traceback (most recent call last):
  File "/usr/lib64/python3.12/site-packages/mysql/connector/connection_cext.py", line 651, in cmd_stmt_execute
    statement_id.stmt_execute(*args, query_attrs=self.query_attrs)
_mysql_connector.MySQLInterfaceError: Error while executing statement: runtime error: index out of range [1] with length 1

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/dvaneeden/./query_attributes.py", line 9, in <module>
    cur.execute("SELECT mysql_query_attribute_string(%s)", ('testattr',))
  File "/usr/lib64/python3.12/site-packages/mysql/connector/cursor_cext.py", line 1229, in execute
    res = self._cnx.cmd_stmt_execute(self._stmt, *params)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/site-packages/mysql/connector/opentelemetry/context_propagation.py", line 102, in wrapper
    return method(cnx, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/site-packages/mysql/connector/connection_cext.py", line 653, in cmd_stmt_execute
    raise InterfaceError(str(err)) from err
mysql.connector.errors.InterfaceError: Error while executing statement: runtime error: index out of range [1] with length 1

Wireshark also fails to decode it: image

This might be because the flags are set to 0x0 instead of 0x8 (PARAMETER_COUNT_AVAILABLE). See also https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_stmt_execute.html

There was an issue with the QueryAttribute in the execute statement protocol handling, which has been fixed.

@dveeden
Copy link
Contributor

dveeden commented Feb 25, 2025

The CI job doesn't seem to be happy about go generate ./... resulting in this:

diff --git a/pkg/util/collate/unicode_0400_ci_generated.go b/pkg/util/collate/unicode_0400_ci_generated.go
index db1ac70e21..c073d4c67a 100644
--- a/pkg/util/collate/unicode_0400_ci_generated.go
+++ b/pkg/util/collate/unicode_0400_ci_generated.go
@@ -28 +28 @@ func (uc *unicodeCICollator) Clone() Collator {
-       return &unicodeCICollator{impl: uc.impl.Clone()}
+    return &unicodeCICollator{impl: uc.impl.Clone()}
diff --git a/pkg/util/collate/unicode_0900_ai_ci_generated.go b/pkg/util/collate/unicode_0900_ai_ci_generated.go
index 51177f893c..5e160c6182 100644
--- a/pkg/util/collate/unicode_0900_ai_ci_generated.go
+++ b/pkg/util/collate/unicode_0900_ai_ci_generated.go
@@ -28 +28 @@ func (uc *unicode0900AICICollator) Clone() Collator {
-       return &unicode0900AICICollator{impl: uc.impl.Clone()}
+    return &unicode0900AICICollator{impl: uc.impl.Clone()}

You should probably run go generate ./... and commit the result. Just formatting these files might also be sufficient.

@kafka1991
Copy link
Contributor Author

The CI job doesn't seem to be happy about go generate ./... resulting in this:

diff --git a/pkg/util/collate/unicode_0400_ci_generated.go b/pkg/util/collate/unicode_0400_ci_generated.go
index db1ac70e21..c073d4c67a 100644
--- a/pkg/util/collate/unicode_0400_ci_generated.go
+++ b/pkg/util/collate/unicode_0400_ci_generated.go
@@ -28 +28 @@ func (uc *unicodeCICollator) Clone() Collator {
-       return &unicodeCICollator{impl: uc.impl.Clone()}
+    return &unicodeCICollator{impl: uc.impl.Clone()}
diff --git a/pkg/util/collate/unicode_0900_ai_ci_generated.go b/pkg/util/collate/unicode_0900_ai_ci_generated.go
index 51177f893c..5e160c6182 100644
--- a/pkg/util/collate/unicode_0900_ai_ci_generated.go
+++ b/pkg/util/collate/unicode_0900_ai_ci_generated.go
@@ -28 +28 @@ func (uc *unicode0900AICICollator) Clone() Collator {
-       return &unicode0900AICICollator{impl: uc.impl.Clone()}
+    return &unicode0900AICICollator{impl: uc.impl.Clone()}

You should probably run go generate ./... and commit the result. Just formatting these files might also be sufficient.

done

@dveeden
Copy link
Contributor

dveeden commented Feb 25, 2025

/retest

Copy link

tiprow bot commented Feb 25, 2025

@kafka1991: The following test failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
fast_test_tiprow 19f2ec8 link true /test fast_test_tiprow

Full PR test history. Your PR dashboard.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

Copy link
Contributor

@dveeden dveeden left a comment

Choose a reason for hiding this comment

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

Test code:

#include <stdio.h>
#include <string.h>
#include <mysql.h>

MYSQL mysql;

int main() {
  MYSQL_BIND bind[13];
  const char *name[13] = {"string", "bigint", "int", "smallint", "tinyint", "float",
	  "double", "time", "date", "datetime", "timestamp", "blob", "null"};
  unsigned int ssl_mode = SSL_MODE_DISABLED;

  mysql_init(&mysql);

  mysql_options(&mysql,MYSQL_OPT_SSL_MODE, &ssl_mode);
  if (!mysql_real_connect(&mysql, "127.0.0.1", "root", "", "test", 4000, NULL, 0)) {
    fprintf(stderr, "Failed to connect to database: Error: %s\n",
            mysql_error(&mysql));
  }

  memset(bind, 0, sizeof(bind));

  char *mystr = "test test";
  unsigned long mystrlen = strlen(mystr);
  bind[0].buffer_type = MYSQL_TYPE_STRING;
  bind[0].buffer = mystr;
  bind[0].length = &mystrlen;
  bind[0].is_null = 0;

  unsigned long long longlongparam = 123ULL;
  unsigned long longlongparamlen = sizeof(long long int);
  bind[1].buffer_type = MYSQL_TYPE_LONGLONG;
  bind[1].buffer = (char *) &longlongparam;
  bind[1].length = &longlongparamlen;
  bind[1].is_null = 0;
  bind[1].is_unsigned = 1;

  int intparam = 123;
  unsigned long intparamlen = sizeof(int);
  bind[2].buffer_type = MYSQL_TYPE_LONG;
  bind[2].buffer = (char *) &intparam;
  bind[2].length = &intparamlen;
  bind[2].is_null = 0;

  short int shortparam = 123;
  unsigned long shortparamlen = sizeof(short int);
  bind[3].buffer_type = MYSQL_TYPE_SHORT;
  bind[3].buffer = (char *) &shortparam;
  bind[3].length = &shortparamlen;
  bind[3].is_null = 0;

  signed char tinyparam = 123;
  unsigned long tinyparamlen = sizeof(signed char);
  bind[4].buffer_type = MYSQL_TYPE_TINY;
  bind[4].buffer = (char *) &tinyparam;
  bind[4].length = &tinyparamlen;
  bind[4].is_null = 0;

  float floatparam = 12.34;
  unsigned long floatparamlen = sizeof(float);
  bind[5].buffer_type = MYSQL_TYPE_FLOAT;
  bind[5].buffer = (char *) &floatparam;
  bind[5].length = &floatparamlen;
  bind[5].is_null = 0;

  double doubleparam = 12.34;
  unsigned long doubleparamlen = sizeof(double);
  bind[6].buffer_type = MYSQL_TYPE_DOUBLE;
  bind[6].buffer = (char *) &doubleparam;
  bind[6].length = &doubleparamlen;
  bind[6].is_null = 0;

  MYSQL_TIME tm;
  tm.hour = 8;
  tm.minute = 23;
  tm.second = 24;
  tm.second_part = 987654;
  unsigned long tmlen = sizeof(tm);
  bind[7].buffer_type = MYSQL_TYPE_TIME;
  bind[7].buffer = (char *) &tm;
  bind[7].length = &tmlen;
  bind[7].is_null = 0;

  MYSQL_TIME ts;
  ts.year = 2025;
  ts.month = 2;
  ts.day = 6;
  ts.hour = 8;
  ts.minute = 23;
  ts.second = 24;
  unsigned long tslen = sizeof(ts);
  bind[8].buffer_type = MYSQL_TYPE_DATE;
  bind[8].buffer = (char *) &ts;
  bind[8].length = &tslen;
  bind[8].is_null = 0;

  bind[9].buffer_type = MYSQL_TYPE_DATETIME;
  bind[9].buffer = (char *) &ts;
  bind[9].length = &tslen;
  bind[9].is_null = 0;

  bind[10].buffer_type = MYSQL_TYPE_TIMESTAMP;
  bind[10].buffer = (char *) &ts;
  bind[10].length = &tslen;
  bind[10].is_null = 0;

  bind[11].buffer_type = MYSQL_TYPE_BLOB;
  bind[11].buffer = mystr;
  bind[11].length = &mystrlen;
  bind[11].is_null = 0;

  bind[12].buffer_type = MYSQL_TYPE_NULL;

  mysql_bind_param(&mysql, 13, bind, name);
  const char *query = "SELECT mysql_query_attribute_string('string'), mysql_query_attribute_string('bigint'), mysql_query_attribute_string('int'), mysql_query_attribute_string('smallint'), mysql_query_attribute_string('tinyint'), mysql_query_attribute_string('float'), mysql_query_attribute_string('double'), mysql_query_attribute_string('time'), mysql_query_attribute_string('date'), mysql_query_attribute_string('datetime'), mysql_query_attribute_string('timestamp'), mysql_query_attribute_string('blob'), mysql_query_attribute_string('null')";
  mysql_real_query(&mysql, query, strlen(query));
  MYSQL_RES *result = mysql_store_result(&mysql);
  MYSQL_ROW row = mysql_fetch_row(result);
  unsigned long *lengths = mysql_fetch_lengths(result);
  for(int i = 0; i < 13; i++)
  {
      printf("attribute %2d (%-9s) [%2ld]: [%.*s]\n", i+1, name[i], bind[i].length == NULL ? 0 : *bind[i].length, (int) lengths[i], row[i] ? row[i] : "NULL");
  }
  mysql_free_result(result);

  mysql_close(&mysql);
}

Compiled with:

cc $(mysql_config --libs --cflags) -o qattr qattr.c

Modified the port of mysql_real_connect where needed to switch between 3306 (MySQL) and 4000 (TiDB).

MySQL:

$ ./qattr 
attribute  1 (string   ) [ 9]: [test test]
attribute  2 (bigint   ) [ 8]: [123]
attribute  3 (int      ) [ 4]: [123]
attribute  4 (smallint ) [ 2]: [123]
attribute  5 (tinyint  ) [ 1]: [123]
attribute  6 (float    ) [ 4]: [12.34000015258789]
attribute  7 (double   ) [ 8]: [12.34]
attribute  8 (time     ) [48]: [08:23:24.987654]
attribute  9 (date     ) [48]: [2025-02-06]
attribute 10 (datetime ) [48]: [2025-02-06 08:23:24.000000]
attribute 11 (timestamp) [48]: [2025-02-06 08:23:24.000000]
attribute 12 (blob     ) [ 9]: [test test]
attribute 13 (null     ) [ 0]: []

TiDB:

$ ./qattr 
attribute  1 (string   ) [ 9]: [test test]
attribute  2 (bigint   ) [ 8]: [123]
attribute  3 (int      ) [ 4]: [123]
attribute  4 (smallint ) [ 2]: [123]
attribute  5 (tinyint  ) [ 1]: [123]
attribute  6 (float    ) [ 4]: [12.34]
attribute  7 (double   ) [ 8]: [12.34]
attribute  8 (time     ) [48]: [08:23:24.987654]
attribute  9 (date     ) [48]: [2025-02-06]
attribute 10 (datetime ) [48]: [2025-02-06 08:23:24]
attribute 11 (timestamp) [48]: [2025-02-06 08:23:24]
attribute 12 (blob     ) [ 9]: [test test]
attribute 13 (null     ) [ 0]: []

Copy link

ti-chi-bot bot commented Feb 25, 2025

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: dveeden
Once this PR has been reviewed and has the lgtm label, please assign bornchanger, xuhuaiyu, yudongusa for approval, ensuring that each of them provides their approval before proceeding. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@ti-chi-bot ti-chi-bot bot added the needs-1-more-lgtm Indicates a PR needs 1 more LGTM. label Feb 25, 2025
Copy link

ti-chi-bot bot commented Feb 25, 2025

[LGTM Timeline notifier]

Timeline:

  • 2024-10-23 07:52:04.499181605 +0000 UTC m=+423925.195972215: ✖️🔁 reset by dveeden.
  • 2025-02-25 12:58:44.779237927 +0000 UTC m=+360672.732396193: ☑️ agreed by dveeden.

Copy link

ti-chi-bot bot commented Feb 25, 2025

@kafka1991: The following test failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
idc-jenkins-ci-tidb/unit-test 19f2ec8 link true /test unit-test

Full PR test history. Your PR dashboard.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

@dveeden
Copy link
Contributor

dveeden commented Feb 25, 2025

Another test:

#!/bin/python
import mysql.connector

for port in [3306, 4000]:
    for prep in [True, False]:
        print(f"Testing against port {port} with prepared={prep}")
        config = {
                "host":"127.0.0.1",
                "port":port,
                "user":"root",
        }
        c = mysql.connector.connect(**config)
        cur = c.cursor(prepared=prep)
        cur.add_attribute("qattrkey", "testvalue🐬")
        cur.execute("SELECT VERSION(), mysql_query_attribute_string('qattrkey')")
        for row in cur:
            print(f"{row[0]:50s} {row[1]}\n")
        cur.close()
        c.close()

Result:

Testing against port 3306 with prepared=True
9.2.0                                              testvalue🐬

Testing against port 3306 with prepared=False
9.2.0                                              testvalue🐬

Testing against port 4000 with prepared=True
8.0.11-TiDB-v9.0.0-alpha-326-g19f2ec858d-dirty     None

Testing against port 4000 with prepared=False
8.0.11-TiDB-v9.0.0-alpha-326-g19f2ec858d-dirty     testvalue🐬

So this doesn't yet work for prepared statements?

@kafka1991
Copy link
Contributor Author

kafka1991 commented Feb 25, 2025

Another test:

#!/bin/python
import mysql.connector

for port in [3306, 4000]:
    for prep in [True, False]:
        print(f"Testing against port {port} with prepared={prep}")
        config = {
                "host":"127.0.0.1",
                "port":port,
                "user":"root",
        }
        c = mysql.connector.connect(**config)
        cur = c.cursor(prepared=prep)
        cur.add_attribute("qattrkey", "testvalue🐬")
        cur.execute("SELECT VERSION(), mysql_query_attribute_string('qattrkey')")
        for row in cur:
            print(f"{row[0]:50s} {row[1]}\n")
        cur.close()
        c.close()

Result:

Testing against port 3306 with prepared=True
9.2.0                                              testvalue🐬

Testing against port 3306 with prepared=False
9.2.0                                              testvalue🐬

Testing against port 4000 with prepared=True
8.0.11-TiDB-v9.0.0-alpha-326-g19f2ec858d-dirty     None

Testing against port 4000 with prepared=False
8.0.11-TiDB-v9.0.0-alpha-326-g19f2ec858d-dirty     testvalue🐬

So this doesn't yet work for prepared statements?

Here is My test result with git head (19f2ec858d9a26eb5f91f59fdc77cf1726100c7f):

Testing against port 4000 with prepared=True
8.0.11-TiDB-v8.4.0-this-is-a-placeholder           testvalue🐬

Testing against port 4000 with prepared=False
8.0.11-TiDB-v8.4.0-this-is-a-placeholder           testvalue🐬

It seems strange. Did you cherry-pick the relevant code to another branch?

@dveeden
Copy link
Contributor

dveeden commented Feb 25, 2025

These are the same commit:

                              19f2ec858d9a26eb5f91f59fdc77cf1726100c7f
8.0.11-TiDB-v9.0.0-alpha-326-g19f2ec858d-dirty
                              ^^^^^^^^^^

Extended the test:

#!/bin/python
import mysql.connector

print(f"Testing with MySQL Connector/Python {mysql.connector.version.VERSION_TEXT}\n")

for port in [3306, 4000]:
    for prep in [True, False]:
        for pure in [True, False]:
            print(
                f"Testing against port {port} with prepared={prep} and use_pure={pure}"
            )
            config = {
                "host": "127.0.0.1",
                "port": port,
                "user": "root",
                "use_pure": pure,
            }
            c = mysql.connector.connect(**config)
            cur = c.cursor(prepared=prep)
            cur.add_attribute("qattrkey", "testvalue🐬")
            cur.execute("SELECT VERSION(), mysql_query_attribute_string('qattrkey')")
            for row in cur:
                print(f"{row[0]:50s} {row[1]}\n")
            cur.close()
            c.close()
dvaneeden@dve-carbon:~/dev/mysql-qattr$ ./qattr.py 
Testing with MySQL Connector/Python 9.2.0

Testing against port 3306 with prepared=True and use_pure=True
9.2.0                                              testvalue🐬

Testing against port 3306 with prepared=True and use_pure=False
9.2.0                                              testvalue🐬

Testing against port 3306 with prepared=False and use_pure=True
9.2.0                                              testvalue🐬

Testing against port 3306 with prepared=False and use_pure=False
9.2.0                                              testvalue🐬

Testing against port 4000 with prepared=True and use_pure=True
8.0.11-TiDB-v9.0.0-alpha-326-g19f2ec858d-dirty     testvalue🐬

Testing against port 4000 with prepared=True and use_pure=False
8.0.11-TiDB-v9.0.0-alpha-326-g19f2ec858d-dirty     None

Testing against port 4000 with prepared=False and use_pure=True
8.0.11-TiDB-v9.0.0-alpha-326-g19f2ec858d-dirty     testvalue🐬

Testing against port 4000 with prepared=False and use_pure=False
8.0.11-TiDB-v9.0.0-alpha-326-g19f2ec858d-dirty     testvalue🐬

@dveeden
Copy link
Contributor

dveeden commented Feb 25, 2025

With the local formatting changes removed:

Testing with MySQL Connector/Python 9.2.0

Testing against port 3306 with prepared=True and use_pure=True
9.2.0                                              testvalue🐬

Testing against port 3306 with prepared=True and use_pure=False
9.2.0                                              testvalue🐬

Testing against port 3306 with prepared=False and use_pure=True
9.2.0                                              testvalue🐬

Testing against port 3306 with prepared=False and use_pure=False
9.2.0                                              testvalue🐬

Testing against port 4000 with prepared=True and use_pure=True
8.0.11-TiDB-v9.0.0-alpha-326-g19f2ec858d           testvalue🐬

Testing against port 4000 with prepared=True and use_pure=False
8.0.11-TiDB-v9.0.0-alpha-326-g19f2ec858d           None

Testing against port 4000 with prepared=False and use_pure=True
8.0.11-TiDB-v9.0.0-alpha-326-g19f2ec858d           testvalue🐬

Testing against port 4000 with prepared=False and use_pure=False
8.0.11-TiDB-v9.0.0-alpha-326-g19f2ec858d           testvalue🐬

@dveeden
Copy link
Contributor

dveeden commented Feb 25, 2025

image

Looks like this might be a bug in MySQL Connector/Python. It looks like it doesn't set the right flags and still send the attributes... not sure why it doesn't do this when connecting to MySQL.

@dveeden
Copy link
Contributor

dveeden commented Feb 25, 2025

I found the issue:

MySQL Connector/Python with the C extensions enabled and with the prepared cursor enabled is using the MySQL C API to send query attributes to the server

This works by:

  • Setting the parameters via mysql_stmt_bind_named_param()
  • Executing via mysql_stmt_execute()

And then there is this:

https://github.com/mysql/mysql-server/blob/6b6d3ed3d5c6591b446276184642d7d0504ecc86/libmysql/libmysql.cc#L1944

This causes the libmysql to incorrectly handle the protocol if the version the server advertises is below 8.0.26.

To test this:

./bin/tidb-server -config <(echo -en "server-version = \"8.0.11-TiDB-v9.0.0-query-attr\"")
./bin/tidb-server -config <(echo -en "server-version = \"8.0.26-TiDB-v9.0.0-query-attr\"")

Results:

dvaneeden@dve-carbon:~/dev/mysql-qattr$ ./qattr.py 
Testing with MySQL Connector/Python 9.2.0

Testing against port 3306 with prepared=True and use_pure=False
9.2.0                                              testvalue🐬

Testing against port 4000 with prepared=True and use_pure=False
8.0.11-TiDB-v9.0.0-query-attr                      None

dvaneeden@dve-carbon:~/dev/mysql-qattr$ ./qattr.py 
Testing with MySQL Connector/Python 9.2.0

Testing against port 3306 with prepared=True and use_pure=False
9.2.0                                              testvalue🐬

Testing against port 4000 with prepared=True and use_pure=False
8.0.26-TiDB-v9.0.0-query-attr                      testvalue🐬

I've created https://bugs.mysql.com/bug.php?id=117567 for this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs-1-more-lgtm Indicates a PR needs 1 more LGTM. ok-to-test Indicates a PR is ready to be tested. release-note-none Denotes a PR that doesn't merit a release note. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support query attributed in client protocol since 8.0.23
6 participants