{"action":"created","comment":{"url":"https://api.github.com/repos/datastax/java-driver/pulls/comments/74908727","id":74908727,"diff_hunk":"@@ -428,45 +460,114 @@ public static String formatTime(long value) {\n     }\n \n     /**\n-     * Simple method to replace a single character.\n-     * {@code String.replace()} is a bit too inefficient (see JAVA-67).\n-     * This methods is specially useful for escaping single quotes\n-     * in CQL strings:\n-     * <p/>\n-     * <pre>{@code\n-     * ParseUtils.replaceChar(value, '\\'', \"''\");\n-     * }</pre>\n+     * Return {@code true} if the given string is surrounded\n+     * by the quote character given, and {@code false} otherwise.\n+     *\n+     * @param value The string to inspect.\n+     * @return {@code true} if the given string is surrounded\n+     * by the quote character, and {@code false} otherwise.\n+     */\n+    private static boolean isQuoted(String value, char quoteChar) {\n+        return value != null && value.length() > 1\n+                && value.charAt(0) == quoteChar && value.charAt(value.length() - 1) == quoteChar;\n+    }\n+\n+    /**\n+     * @param quoteChar \" or '\n+     * @return A quoted empty string.\n+     */\n+    private static String emptyQuoted(char quoteChar) {\n+        // don't handle non quote characters, this is done so the these are interned and doesn't create\n+        // repeated empty quoted strings.\n+        assert quoteChar == '\"' || quoteChar == '\\'';\n+        if (quoteChar == '\"')\n+            return \"\\\"\\\"\";\n+        else\n+            return \"''\";\n+    }\n+\n+\n+    /**\n+     * Quotes text and escapes any existing quotes in the text.\n+     * {@code String.replace()} is a bit too inefficient (see JAVA-67, JAVA-1262).\n      *\n-     * @param text        The text.\n-     * @param search      The character to search for.\n-     * @param replacement The replacement.\n-     * @return The text with all occurrences of {@code search} replaced with {@code replacement}.\n+     * @param text      The text.\n+     * @param quoteChar The character to use as a quote.\n+     * @return The text with surrounded in quotes with all existing quotes escaped with (i.e. ' becomes '')\n      */\n-    private static String replaceChar(String text, char search, String replacement) {\n+    private static String quote(String text, char quoteChar) {\n         if (text == null || text.isEmpty())\n-            return \"\";\n+            return emptyQuoted(quoteChar);\n \n         int nbMatch = 0;\n         int start = -1;\n         do {\n-            start = text.indexOf(search, start + 1);\n+            start = text.indexOf(quoteChar, start + 1);\n             if (start != -1)\n                 ++nbMatch;\n         } while (start != -1);\n \n-        if (nbMatch == 0)\n+        // 2 for beginning and end quotes.\n+        // length for original text\n+        // nbMatch for escape characters to add to quotes to be escaped.\n+        int newLength = 2 + text.length() + nbMatch;\n+        char[] result = new char[newLength];\n+        result[0] = quoteChar;\n+        result[newLength - 1] = quoteChar;\n+        int newIdx = 1;\n+        for (int i = 0; i < text.length(); i++) {\n+            char c = text.charAt(i);\n+            if (c == quoteChar) {\n+                // escape quote with another occurrence.\n+                result[newIdx++] = c;\n+                result[newIdx++] = c;\n+            } else {\n+                result[newIdx++] = c;\n+            }\n+        }\n+        return new String(result);\n+    }\n+\n+    /**\n+     * Unquotes text and unescapes non surrounding quotes.\n+     * {@code String.replace()} is a bit too inefficient (see JAVA-67, JAVA-1262).\n+     *\n+     * @param text      The text\n+     * @param quoteChar The character to use as a quote.\n+     * @return The text with surrounding quotes removed and non surrounding quotes unescaped (i.e. '' becomes ')\n+     */\n+    private static String unquote(String text, char quoteChar) {\n+        if (!isQuoted(text, quoteChar))\n             return text;\n \n-        int newLength = text.length() + nbMatch * (replacement.length() - 1);\n+        String search = emptyQuoted(quoteChar);\n+        int nbMatch = 0;\n+        int start = -2;\n+        do {\n+            start = text.indexOf(search, start + 2);\n+            // ignore the second to last character occurrence, as the last character is a quote.\n+            if (start != -1 && start != text.length() - 2)\n+                ++nbMatch;\n+        } while (start != -1);\n+\n+        // length of the new string will be its current length - the number of occurrences.\n+        int newLength = text.length() - nbMatch - 2;\n         char[] result = new char[newLength];\n         int newIdx = 0;\n-        for (int i = 0; i < text.length(); i++) {\n+        boolean firstFound = false;\n+        for (int i = 1; i < text.length() - 1; i++) {\n             char c = text.charAt(i);\n-            if (c == search) {\n-                for (int r = 0; r < replacement.length(); r++)\n-                    result[newIdx++] = replacement.charAt(r);\n+            if (c == quoteChar) {\n+                if (firstFound) {\n+                    // don't add to result, this action in effect removes consecutive characters.\n+                    firstFound = false;\n+                } else {\n+                    firstFound = true;\n+                    result[newIdx++] = c;\n+                }\n             } else {\n                 result[newIdx++] = c;\n+                firstFound = false;","path":"driver-core/src/main/java/com/datastax/driver/core/ParseUtils.java","position":195,"original_position":195,"commit_id":"ca2604e4e302dbee379cc3961b729afe995e73af","original_commit_id":"ca2604e4e302dbee379cc3961b729afe995e73af","user":{"login":"newkek","id":3471564,"avatar_url":"https://avatars.githubusercontent.com/u/3471564?v=3","gravatar_id":"","url":"https://api.github.com/users/newkek","html_url":"https://github.com/newkek","followers_url":"https://api.github.com/users/newkek/followers","following_url":"https://api.github.com/users/newkek/following{/other_user}","gists_url":"https://api.github.com/users/newkek/gists{/gist_id}","starred_url":"https://api.github.com/users/newkek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/newkek/subscriptions","organizations_url":"https://api.github.com/users/newkek/orgs","repos_url":"https://api.github.com/users/newkek/repos","events_url":"https://api.github.com/users/newkek/events{/privacy}","received_events_url":"https://api.github.com/users/newkek/received_events","type":"User","site_admin":false},"body":"Shouldn't it be set to `true`? Unless I'm misunderstanding something","created_at":"2016-08-16T10:13:43Z","updated_at":"2016-08-16T10:13:43Z","html_url":"https://github.com/datastax/java-driver/pull/728#discussion_r74908727","pull_request_url":"https://api.github.com/repos/datastax/java-driver/pulls/728","_links":{"self":{"href":"https://api.github.com/repos/datastax/java-driver/pulls/comments/74908727"},"html":{"href":"https://github.com/datastax/java-driver/pull/728#discussion_r74908727"},"pull_request":{"href":"https://api.github.com/repos/datastax/java-driver/pulls/728"}}},"pull_request":{"url":"https://api.github.com/repos/datastax/java-driver/pulls/728","id":81169505,"html_url":"https://github.com/datastax/java-driver/pull/728","diff_url":"https://github.com/datastax/java-driver/pull/728.diff","patch_url":"https://github.com/datastax/java-driver/pull/728.patch","issue_url":"https://api.github.com/repos/datastax/java-driver/issues/728","number":728,"state":"open","locked":false,"title":"JAVA-1262: Use ParseUtils for quoting & unquoting","user":{"login":"tolbertam","id":6889771,"avatar_url":"https://avatars.githubusercontent.com/u/6889771?v=3","gravatar_id":"","url":"https://api.github.com/users/tolbertam","html_url":"https://github.com/tolbertam","followers_url":"https://api.github.com/users/tolbertam/followers","following_url":"https://api.github.com/users/tolbertam/following{/other_user}","gists_url":"https://api.github.com/users/tolbertam/gists{/gist_id}","starred_url":"https://api.github.com/users/tolbertam/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tolbertam/subscriptions","organizations_url":"https://api.github.com/users/tolbertam/orgs","repos_url":"https://api.github.com/users/tolbertam/repos","events_url":"https://api.github.com/users/tolbertam/events{/privacy}","received_events_url":"https://api.github.com/users/tolbertam/received_events","type":"User","site_admin":false},"body":"For [JAVA-1262](https://datastax-oss.atlassian.net/browse/JAVA-1262)\r\n\r\nMetadata.handleId and quote use String.replace or replaceAll, which each\r\ncreate a lot of garbage as they compile a Pattern each time.  Instead\r\nuse ParseUtils.quote and unquote for quoting and unquoting.\r\n\r\nAdds doubleQuote, unDoubleQuote, and isDoubleQuoted as analogs to the\r\nsingle quoted counterparts in ParseUtils.\r\n\r\nMemory allocation difference running a 5,000,000 write cassandra-stress:\r\n\r\n| configuration | Metadata.quote() | Metadata.handleId() | total       | % of total |\r\n| ------------- | ----------------- | -------------------- | --------  | --------- |\r\n| 3.0.x               | 3.18GB                 | 3.77GB                      | 43.54GB | 15.96%    |\r\n| java1262         | 496.47MB           | 1.30GB                      | 36.69GB | 4.87%    |\r\n\r\nA good portion of Metadata.handleId() (880.63 MB) is from pattern checking for determining whether or not to lowercase a non-quoted string.","created_at":"2016-08-12T19:19:35Z","updated_at":"2016-08-16T10:13:43Z","closed_at":null,"merged_at":null,"merge_commit_sha":"f8acb121a705b182a999c1868f928f530e19612e","assignee":null,"assignees":[],"milestone":{"url":"https://api.github.com/repos/datastax/java-driver/milestones/18","html_url":"https://github.com/datastax/java-driver/milestones/3.0.4","labels_url":"https://api.github.com/repos/datastax/java-driver/milestones/18/labels","id":1918369,"number":18,"title":"3.0.4","description":null,"creator":{"login":"adutra","id":463876,"avatar_url":"https://avatars.githubusercontent.com/u/463876?v=3","gravatar_id":"","url":"https://api.github.com/users/adutra","html_url":"https://github.com/adutra","followers_url":"https://api.github.com/users/adutra/followers","following_url":"https://api.github.com/users/adutra/following{/other_user}","gists_url":"https://api.github.com/users/adutra/gists{/gist_id}","starred_url":"https://api.github.com/users/adutra/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/adutra/subscriptions","organizations_url":"https://api.github.com/users/adutra/orgs","repos_url":"https://api.github.com/users/adutra/repos","events_url":"https://api.github.com/users/adutra/events{/privacy}","received_events_url":"https://api.github.com/users/adutra/received_events","type":"User","site_admin":false},"open_issues":6,"closed_issues":0,"state":"open","created_at":"2016-08-02T14:03:15Z","updated_at":"2016-08-15T19:57:21Z","due_on":null,"closed_at":null},"commits_url":"https://api.github.com/repos/datastax/java-driver/pulls/728/commits","review_comments_url":"https://api.github.com/repos/datastax/java-driver/pulls/728/comments","review_comment_url":"https://api.github.com/repos/datastax/java-driver/pulls/comments{/number}","comments_url":"https://api.github.com/repos/datastax/java-driver/issues/728/comments","statuses_url":"https://api.github.com/repos/datastax/java-driver/statuses/ca2604e4e302dbee379cc3961b729afe995e73af","head":{"label":"datastax:java1262","ref":"java1262","sha":"ca2604e4e302dbee379cc3961b729afe995e73af","user":{"login":"datastax","id":573369,"avatar_url":"https://avatars.githubusercontent.com/u/573369?v=3","gravatar_id":"","url":"https://api.github.com/users/datastax","html_url":"https://github.com/datastax","followers_url":"https://api.github.com/users/datastax/followers","following_url":"https://api.github.com/users/datastax/following{/other_user}","gists_url":"https://api.github.com/users/datastax/gists{/gist_id}","starred_url":"https://api.github.com/users/datastax/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/datastax/subscriptions","organizations_url":"https://api.github.com/users/datastax/orgs","repos_url":"https://api.github.com/users/datastax/repos","events_url":"https://api.github.com/users/datastax/events{/privacy}","received_events_url":"https://api.github.com/users/datastax/received_events","type":"Organization","site_admin":false},"repo":{"id":6765281,"name":"java-driver","full_name":"datastax/java-driver","owner":{"login":"datastax","id":573369,"avatar_url":"https://avatars.githubusercontent.com/u/573369?v=3","gravatar_id":"","url":"https://api.github.com/users/datastax","html_url":"https://github.com/datastax","followers_url":"https://api.github.com/users/datastax/followers","following_url":"https://api.github.com/users/datastax/following{/other_user}","gists_url":"https://api.github.com/users/datastax/gists{/gist_id}","starred_url":"https://api.github.com/users/datastax/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/datastax/subscriptions","organizations_url":"https://api.github.com/users/datastax/orgs","repos_url":"https://api.github.com/users/datastax/repos","events_url":"https://api.github.com/users/datastax/events{/privacy}","received_events_url":"https://api.github.com/users/datastax/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/datastax/java-driver","description":"DataStax Java Driver for Apache Cassandra","fork":false,"url":"https://api.github.com/repos/datastax/java-driver","forks_url":"https://api.github.com/repos/datastax/java-driver/forks","keys_url":"https://api.github.com/repos/datastax/java-driver/keys{/key_id}","collaborators_url":"https://api.github.com/repos/datastax/java-driver/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/datastax/java-driver/teams","hooks_url":"https://api.github.com/repos/datastax/java-driver/hooks","issue_events_url":"https://api.github.com/repos/datastax/java-driver/issues/events{/number}","events_url":"https://api.github.com/repos/datastax/java-driver/events","assignees_url":"https://api.github.com/repos/datastax/java-driver/assignees{/user}","branches_url":"https://api.github.com/repos/datastax/java-driver/branches{/branch}","tags_url":"https://api.github.com/repos/datastax/java-driver/tags","blobs_url":"https://api.github.com/repos/datastax/java-driver/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/datastax/java-driver/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/datastax/java-driver/git/refs{/sha}","trees_url":"https://api.github.com/repos/datastax/java-driver/git/trees{/sha}","statuses_url":"https://api.github.com/repos/datastax/java-driver/statuses/{sha}","languages_url":"https://api.github.com/repos/datastax/java-driver/languages","stargazers_url":"https://api.github.com/repos/datastax/java-driver/stargazers","contributors_url":"https://api.github.com/repos/datastax/java-driver/contributors","subscribers_url":"https://api.github.com/repos/datastax/java-driver/subscribers","subscription_url":"https://api.github.com/repos/datastax/java-driver/subscription","commits_url":"https://api.github.com/repos/datastax/java-driver/commits{/sha}","git_commits_url":"https://api.github.com/repos/datastax/java-driver/git/commits{/sha}","comments_url":"https://api.github.com/repos/datastax/java-driver/comments{/number}","issue_comment_url":"https://api.github.com/repos/datastax/java-driver/issues/comments{/number}","contents_url":"https://api.github.com/repos/datastax/java-driver/contents/{+path}","compare_url":"https://api.github.com/repos/datastax/java-driver/compare/{base}...{head}","merges_url":"https://api.github.com/repos/datastax/java-driver/merges","archive_url":"https://api.github.com/repos/datastax/java-driver/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/datastax/java-driver/downloads","issues_url":"https://api.github.com/repos/datastax/java-driver/issues{/number}","pulls_url":"https://api.github.com/repos/datastax/java-driver/pulls{/number}","milestones_url":"https://api.github.com/repos/datastax/java-driver/milestones{/number}","notifications_url":"https://api.github.com/repos/datastax/java-driver/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/datastax/java-driver/labels{/name}","releases_url":"https://api.github.com/repos/datastax/java-driver/releases{/id}","deployments_url":"https://api.github.com/repos/datastax/java-driver/deployments","created_at":"2012-11-19T18:44:41Z","updated_at":"2016-08-16T05:32:00Z","pushed_at":"2016-08-15T20:13:16Z","git_url":"git://github.com/datastax/java-driver.git","ssh_url":"git@github.com:datastax/java-driver.git","clone_url":"https://github.com/datastax/java-driver.git","svn_url":"https://github.com/datastax/java-driver","homepage":null,"size":33393,"stargazers_count":685,"watchers_count":685,"language":"Java","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":490,"mirror_url":null,"open_issues_count":37,"forks":490,"open_issues":37,"watchers":685,"default_branch":"3.0"}},"base":{"label":"datastax:3.0.x","ref":"3.0.x","sha":"53569b1a6bd18ec94a0ee6767390a6142dfd1b97","user":{"login":"datastax","id":573369,"avatar_url":"https://avatars.githubusercontent.com/u/573369?v=3","gravatar_id":"","url":"https://api.github.com/users/datastax","html_url":"https://github.com/datastax","followers_url":"https://api.github.com/users/datastax/followers","following_url":"https://api.github.com/users/datastax/following{/other_user}","gists_url":"https://api.github.com/users/datastax/gists{/gist_id}","starred_url":"https://api.github.com/users/datastax/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/datastax/subscriptions","organizations_url":"https://api.github.com/users/datastax/orgs","repos_url":"https://api.github.com/users/datastax/repos","events_url":"https://api.github.com/users/datastax/events{/privacy}","received_events_url":"https://api.github.com/users/datastax/received_events","type":"Organization","site_admin":false},"repo":{"id":6765281,"name":"java-driver","full_name":"datastax/java-driver","owner":{"login":"datastax","id":573369,"avatar_url":"https://avatars.githubusercontent.com/u/573369?v=3","gravatar_id":"","url":"https://api.github.com/users/datastax","html_url":"https://github.com/datastax","followers_url":"https://api.github.com/users/datastax/followers","following_url":"https://api.github.com/users/datastax/following{/other_user}","gists_url":"https://api.github.com/users/datastax/gists{/gist_id}","starred_url":"https://api.github.com/users/datastax/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/datastax/subscriptions","organizations_url":"https://api.github.com/users/datastax/orgs","repos_url":"https://api.github.com/users/datastax/repos","events_url":"https://api.github.com/users/datastax/events{/privacy}","received_events_url":"https://api.github.com/users/datastax/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/datastax/java-driver","description":"DataStax Java Driver for Apache Cassandra","fork":false,"url":"https://api.github.com/repos/datastax/java-driver","forks_url":"https://api.github.com/repos/datastax/java-driver/forks","keys_url":"https://api.github.com/repos/datastax/java-driver/keys{/key_id}","collaborators_url":"https://api.github.com/repos/datastax/java-driver/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/datastax/java-driver/teams","hooks_url":"https://api.github.com/repos/datastax/java-driver/hooks","issue_events_url":"https://api.github.com/repos/datastax/java-driver/issues/events{/number}","events_url":"https://api.github.com/repos/datastax/java-driver/events","assignees_url":"https://api.github.com/repos/datastax/java-driver/assignees{/user}","branches_url":"https://api.github.com/repos/datastax/java-driver/branches{/branch}","tags_url":"https://api.github.com/repos/datastax/java-driver/tags","blobs_url":"https://api.github.com/repos/datastax/java-driver/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/datastax/java-driver/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/datastax/java-driver/git/refs{/sha}","trees_url":"https://api.github.com/repos/datastax/java-driver/git/trees{/sha}","statuses_url":"https://api.github.com/repos/datastax/java-driver/statuses/{sha}","languages_url":"https://api.github.com/repos/datastax/java-driver/languages","stargazers_url":"https://api.github.com/repos/datastax/java-driver/stargazers","contributors_url":"https://api.github.com/repos/datastax/java-driver/contributors","subscribers_url":"https://api.github.com/repos/datastax/java-driver/subscribers","subscription_url":"https://api.github.com/repos/datastax/java-driver/subscription","commits_url":"https://api.github.com/repos/datastax/java-driver/commits{/sha}","git_commits_url":"https://api.github.com/repos/datastax/java-driver/git/commits{/sha}","comments_url":"https://api.github.com/repos/datastax/java-driver/comments{/number}","issue_comment_url":"https://api.github.com/repos/datastax/java-driver/issues/comments{/number}","contents_url":"https://api.github.com/repos/datastax/java-driver/contents/{+path}","compare_url":"https://api.github.com/repos/datastax/java-driver/compare/{base}...{head}","merges_url":"https://api.github.com/repos/datastax/java-driver/merges","archive_url":"https://api.github.com/repos/datastax/java-driver/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/datastax/java-driver/downloads","issues_url":"https://api.github.com/repos/datastax/java-driver/issues{/number}","pulls_url":"https://api.github.com/repos/datastax/java-driver/pulls{/number}","milestones_url":"https://api.github.com/repos/datastax/java-driver/milestones{/number}","notifications_url":"https://api.github.com/repos/datastax/java-driver/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/datastax/java-driver/labels{/name}","releases_url":"https://api.github.com/repos/datastax/java-driver/releases{/id}","deployments_url":"https://api.github.com/repos/datastax/java-driver/deployments","created_at":"2012-11-19T18:44:41Z","updated_at":"2016-08-16T05:32:00Z","pushed_at":"2016-08-15T20:13:16Z","git_url":"git://github.com/datastax/java-driver.git","ssh_url":"git@github.com:datastax/java-driver.git","clone_url":"https://github.com/datastax/java-driver.git","svn_url":"https://github.com/datastax/java-driver","homepage":null,"size":33393,"stargazers_count":685,"watchers_count":685,"language":"Java","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":490,"mirror_url":null,"open_issues_count":37,"forks":490,"open_issues":37,"watchers":685,"default_branch":"3.0"}},"_links":{"self":{"href":"https://api.github.com/repos/datastax/java-driver/pulls/728"},"html":{"href":"https://github.com/datastax/java-driver/pull/728"},"issue":{"href":"https://api.github.com/repos/datastax/java-driver/issues/728"},"comments":{"href":"https://api.github.com/repos/datastax/java-driver/issues/728/comments"},"review_comments":{"href":"https://api.github.com/repos/datastax/java-driver/pulls/728/comments"},"review_comment":{"href":"https://api.github.com/repos/datastax/java-driver/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/datastax/java-driver/pulls/728/commits"},"statuses":{"href":"https://api.github.com/repos/datastax/java-driver/statuses/ca2604e4e302dbee379cc3961b729afe995e73af"}}}}
{"actor":{"display_login":"newkek"}}