diff --git a/CHANGES.rst b/CHANGES.rst
index d7ab0d5..4646d49 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -4,6 +4,9 @@ Changelog
 3.3 (unreleased)
 ----------------
 
+- Restore ``sql_quote`` behavior of always returning native strings
+  (`#54 <https://github.com/zopefoundation/DocumentTemplate/issues/54>`_)
+
 
 3.2.3 (2020-05-28)
 ------------------
diff --git a/src/DocumentTemplate/DT_Var.py b/src/DocumentTemplate/DT_Var.py
index feaab92..32d535e 100644
--- a/src/DocumentTemplate/DT_Var.py
+++ b/src/DocumentTemplate/DT_Var.py
@@ -518,45 +518,26 @@ def structured_text(v, name='(Unknown name)', md={}):
     return HTML()(doc, level, header=False)
 
 
-# Searching and replacing a byte in text, or text in bytes,
-# may give various errors on Python 2 or 3.  So we make separate functions
-REMOVE_BYTES = (b'\x00', b'\x1a', b'\r')
-REMOVE_TEXT = (u'\x00', u'\x1a', u'\r')
-DOUBLE_BYTES = (b"'",)
-DOUBLE_TEXT = (u"'",)
-
-
-def bytes_sql_quote(v):
-    # Helper function for sql_quote, handling only bytes.
-    # Remove bad characters.
-    for char in REMOVE_BYTES:
-        v = v.replace(char, b'')
-    # Double untrusted characters to make them harmless.
-    for char in DOUBLE_BYTES:
-        v = v.replace(char, char * 2)
-    return v
-
-
-def text_sql_quote(v):
-    # Helper function for sql_quote, handling only text.
-    # Remove bad characters.
-    for char in REMOVE_TEXT:
-        v = v.replace(char, u'')
-    # Double untrusted characters to make them harmless.
-    for char in DOUBLE_TEXT:
-        v = v.replace(char, char * 2)
-    return v
-
-
 def sql_quote(v, name='(Unknown name)', md={}):
     """Quote single quotes in a string by doubling them.
 
     This is needed to securely insert values into sql
     string literals in templates that generate sql.
     """
-    if isinstance(v, bytes):
-        return bytes_sql_quote(v)
-    return text_sql_quote(v)
+    if six.PY3 and isinstance(v, bytes):
+        v = v.decode('UTF-8')
+    elif six.PY2 and not isinstance(v, bytes):
+        v = v.encode('UTF-8')
+
+    # Remove bad characters
+    for char in ('\x00', '\x1a', '\r'):
+        v = v.replace(char, '')
+
+    # Double untrusted characters to make them harmless.
+    for char in ("'",):
+        v = v.replace(char, char * 2)
+
+    return v
 
 
 special_formats = {

diff --git a/src/DocumentTemplate/tests/testDTML.py b/src/DocumentTemplate/tests/testDTML.py
index eea5877..ce516c4 100644
--- a/src/DocumentTemplate/tests/testDTML.py
+++ b/src/DocumentTemplate/tests/testDTML.py
@@ -15,6 +15,8 @@
 
 import unittest
 
+import six
+
 from ..html_quote import html_quote
 
 
@@ -268,6 +270,26 @@ class DTMLTests(unittest.TestCase):
         self.assertEqual(html1(), expected)
         self.assertEqual(html2(), expected)
 
+    def test_sql_quote(self):
+        html = self.doc_class('<dtml-var x sql_quote>')
+        special = u'\xae'
+
+        self.assertEqual(html(x=u'x'), u'x')
+        self.assertEqual(html(x=b'x'), u'x')
+        self.assertEqual(html(x=u"Moe's Bar"), u"Moe''s Bar")
+        self.assertEqual(html(x=b"Moe's Bar"), u"Moe''s Bar")
+
+        if six.PY3:
+            self.assertEqual(html(x=u"Moe's B%sr" % special),
+                             u"Moe''s B%sr" % special)
+            self.assertEqual(html(x=b"Moe's B%sr" % special.encode('UTF-8')),
+                             u"Moe''s B%sr" % special)
+        else:
+            self.assertEqual(html(x=u"Moe's B%sr" % special),
+                             "Moe''s B%sr" % special.encode('UTF-8'))
+            self.assertEqual(html(x=b"Moe's B%sr" % special.encode('UTF-8')),
+                             b"Moe''s B%sr" % special.encode('UTF-8'))
+
     def test_fmt(self):
         html = self.doc_class(
             """
diff --git a/src/DocumentTemplate/tests/test_DT_Var.py b/src/DocumentTemplate/tests/test_DT_Var.py
index 45d7925..648af1b 100644
--- a/src/DocumentTemplate/tests/test_DT_Var.py
+++ b/src/DocumentTemplate/tests/test_DT_Var.py
@@ -15,6 +15,8 @@
 
 import unittest
 
+import six
+
 
 class TestNewlineToBr(unittest.TestCase):
 
@@ -95,63 +97,11 @@ class TestUrlQuoting(unittest.TestCase):
         self.assertEqual(
             url_unquote_plus(quoted_utf8_value), utf8_value)
 
-    def test_bytes_sql_quote(self):
-        from DocumentTemplate.DT_Var import bytes_sql_quote
-        self.assertEqual(bytes_sql_quote(b""), b"")
-        self.assertEqual(bytes_sql_quote(b"a"), b"a")
-
-        self.assertEqual(bytes_sql_quote(b"Can't"), b"Can''t")
-        self.assertEqual(bytes_sql_quote(b"Can\'t"), b"Can''t")
-        self.assertEqual(bytes_sql_quote(br"Can\'t"), b"Can\\''t")
-
-        self.assertEqual(bytes_sql_quote(b"Can\\ I?"), b"Can\\ I?")
-        self.assertEqual(bytes_sql_quote(br"Can\ I?"), b"Can\\ I?")
-
-        self.assertEqual(
-            bytes_sql_quote(b'Just say "Hello"'), b'Just say "Hello"')
-
-        self.assertEqual(
-            bytes_sql_quote(b'Hello\x00World'), b'HelloWorld')
-        self.assertEqual(
-            bytes_sql_quote(b'\x00Hello\x00\x00World\x00'), b'HelloWorld')
-
-        self.assertEqual(
-            bytes_sql_quote(b"carriage\rreturn"), b"carriagereturn")
-        self.assertEqual(bytes_sql_quote(b"line\nbreak"), b"line\nbreak")
-        self.assertEqual(bytes_sql_quote(b"tab\t"), b"tab\t")
-
-    def test_text_sql_quote(self):
-        from DocumentTemplate.DT_Var import text_sql_quote
-        self.assertEqual(text_sql_quote(u""), u"")
-        self.assertEqual(text_sql_quote(u"a"), u"a")
-
-        self.assertEqual(text_sql_quote(u"Can't"), u"Can''t")
-        self.assertEqual(text_sql_quote(u"Can\'t"), u"Can''t")
-        # SyntaxError on Python 3.
-        # self.assertEqual(text_sql_quote(ur"Can\'t"), u"Can\\\\''t")
-
-        self.assertEqual(text_sql_quote(u"Can\\ I?"), u"Can\\ I?")
-        # SyntaxError on Python 3.
-        # self.assertEqual(text_sql_quote(ur"Can\ I?"), u"Can\\\\ I?")
-
-        self.assertEqual(
-            text_sql_quote(u'Just say "Hello"'), u'Just say "Hello"')
-
-        self.assertEqual(
-            text_sql_quote(u'Hello\x00World'), u'HelloWorld')
-        self.assertEqual(
-            text_sql_quote(u'\x00Hello\x00\x00World\x00'), u'HelloWorld')
-
-        self.assertEqual(
-            text_sql_quote(u"carriage\rreturn"), u"carriagereturn")
-        self.assertEqual(text_sql_quote(u"line\nbreak"), u"line\nbreak")
-        self.assertEqual(text_sql_quote(u"tab\t"), u"tab\t")
-
     def test_sql_quote(self):
         from DocumentTemplate.DT_Var import sql_quote
         self.assertEqual(sql_quote(u""), u"")
         self.assertEqual(sql_quote(u"a"), u"a")
-        self.assertEqual(sql_quote(b"a"), b"a")
+        self.assertEqual(sql_quote(b"a"), u"a")
 
         self.assertEqual(sql_quote(u"Can't"), u"Can''t")
         self.assertEqual(sql_quote(u"Can\'t"), u"Can''t")
@@ -174,11 +124,15 @@ class TestUrlQuoting(unittest.TestCase):
             sql_quote(u'\x00Hello\x00\x00World\x00'), u'HelloWorld')
 
         self.assertEqual(u"\xea".encode("utf-8"), b"\xc3\xaa")
-        self.assertEqual(sql_quote(u"\xea'"), u"\xea''")
-        self.assertEqual(sql_quote(b"\xc3\xaa'"), b"\xc3\xaa''")
+        if six.PY3:
+            self.assertEqual(sql_quote(b"\xc3\xaa'"), u"\xea''")
+            self.assertEqual(sql_quote(u"\xea'"), u"\xea''")
+        else:
+            self.assertEqual(sql_quote(b"\xc3\xaa'"), b"\xc3\xaa''")
+            self.assertEqual(sql_quote(u"\xea'"), b"\xc3\xaa''")
 
         self.assertEqual(
-            sql_quote(b"carriage\rreturn"), b"carriagereturn")
+            sql_quote(b"carriage\rreturn"), u"carriagereturn")
         self.assertEqual(
             sql_quote(u"carriage\rreturn"), u"carriagereturn")
         self.assertEqual(sql_quote(u"line\nbreak"), u"line\nbreak")

bytes_sql_quote() returns Bytes, although the content will be part of an SQL query String
In `DocumentTemplate/DT_Var.py`, the helper function `bytes_sql_quote()` is used to remove dangerous content from Bytes when used in SQL queries (edited after correction from @dataflake).

Because this escaped content is returned as Bytes, not String, it is impossible to correctly quote Bytes with `<dtml-sqlvar xy type="string">`, because the two cannot be concatenated.

This patch works for us:

    *** zope4_orig/lib/python3.8/site-packages/DocumentTemplate/DT_Var.py	2020-06-24 08:48:39.767829123 +0200
    --- zope4/lib/python3.8/site-packages/DocumentTemplate/DT_Var.py	2020-06-24 11:06:30.294957406 +0200
    ***************
    *** 534,540 ****
          # Double untrusted characters to make them harmless.
          for char in DOUBLE_BYTES:
              v = v.replace(char, char * 2)
    !     return v


      def text_sql_quote(v):
    --- 534,544 ----
          # Double untrusted characters to make them harmless.
          for char in DOUBLE_BYTES:
              v = v.replace(char, char * 2)
    !     # We return a string because the quoted material will be
    !     # concatenated with other strings to form the query.
    !     # If the eighth bit is set, we interpret as UTF-8, just
    !     # because that encoding is to amazingly popular...
    !     return v.decode('utf-8')


      def text_sql_quote(v):
src/DocumentTemplate/tests/testDTML.py::DTMLTests::test_sql_quote src/DocumentTemplate/tests/testDTML.py::RESTTests::test_sql_quote src/DocumentTemplate/tests/test_DT_Var.py::TestUrlQuoting::test_sql_quote
src/DocumentTemplate/tests/testDTML.py::DTMLTests::testBasicHTMLIn src/DocumentTemplate/tests/testDTML.py::DTMLTests::testBasicHTMLIn2 src/DocumentTemplate/tests/testDTML.py::DTMLTests::testBasicHTMLIn3 src/DocumentTemplate/tests/testDTML.py::DTMLTests::testBasicStringIn src/DocumentTemplate/tests/testDTML.py::DTMLTests::testBatchingEtc src/DocumentTemplate/tests/testDTML.py::DTMLTests::testDTMLDateFormatting src/DocumentTemplate/tests/testDTML.py::DTMLTests::testHTMLInElse src/DocumentTemplate/tests/testDTML.py::DTMLTests::testNoItemPush src/DocumentTemplate/tests/testDTML.py::DTMLTests::testNull src/DocumentTemplate/tests/testDTML.py::DTMLTests::testPropogatedError src/DocumentTemplate/tests/testDTML.py::DTMLTests::testRaise src/DocumentTemplate/tests/testDTML.py::DTMLTests::testRenderCallable src/DocumentTemplate/tests/testDTML.py::DTMLTests::testSequence1 src/DocumentTemplate/tests/testDTML.py::DTMLTests::testSequence2 src/DocumentTemplate/tests/testDTML.py::DTMLTests::testSequenceSummaries src/DocumentTemplate/tests/testDTML.py::DTMLTests::testSimpleString src/DocumentTemplate/tests/testDTML.py::DTMLTests::testSkipQuote src/DocumentTemplate/tests/testDTML.py::DTMLTests::testStringDateFormatting src/DocumentTemplate/tests/testDTML.py::DTMLTests::testSyntaxErrorHandling src/DocumentTemplate/tests/testDTML.py::DTMLTests::testUrlUnquote src/DocumentTemplate/tests/testDTML.py::DTMLTests::testWith src/DocumentTemplate/tests/testDTML.py::DTMLTests::test_fmt src/DocumentTemplate/tests/testDTML.py::RESTTests::testBasicHTMLIn src/DocumentTemplate/tests/testDTML.py::RESTTests::testBasicHTMLIn2 src/DocumentTemplate/tests/testDTML.py::RESTTests::testBasicHTMLIn3 src/DocumentTemplate/tests/testDTML.py::RESTTests::testBasicStringIn src/DocumentTemplate/tests/testDTML.py::RESTTests::testBatchingEtc src/DocumentTemplate/tests/testDTML.py::RESTTests::testDTMLDateFormatting src/DocumentTemplate/tests/testDTML.py::RESTTests::testHTMLInElse src/DocumentTemplate/tests/testDTML.py::RESTTests::testNoItemPush src/DocumentTemplate/tests/testDTML.py::RESTTests::testNull src/DocumentTemplate/tests/testDTML.py::RESTTests::testPropogatedError src/DocumentTemplate/tests/testDTML.py::RESTTests::testRaise src/DocumentTemplate/tests/testDTML.py::RESTTests::testRenderCallable src/DocumentTemplate/tests/testDTML.py::RESTTests::testSequence1 src/DocumentTemplate/tests/testDTML.py::RESTTests::testSequence2 src/DocumentTemplate/tests/testDTML.py::RESTTests::testSequenceSummaries src/DocumentTemplate/tests/testDTML.py::RESTTests::testSimpleString src/DocumentTemplate/tests/testDTML.py::RESTTests::testSkipQuote src/DocumentTemplate/tests/testDTML.py::RESTTests::testStringDateFormatting src/DocumentTemplate/tests/testDTML.py::RESTTests::testSyntaxErrorHandling src/DocumentTemplate/tests/testDTML.py::RESTTests::testUrlUnquote src/DocumentTemplate/tests/testDTML.py::RESTTests::testWith src/DocumentTemplate/tests/testDTML.py::RESTTests::test_fmt src/DocumentTemplate/tests/testDTML.py::test_suite src/DocumentTemplate/tests/test_DT_Var.py::TestNewlineToBr::test_newline_to_br src/DocumentTemplate/tests/test_DT_Var.py::TestNewlineToBr::test_newline_to_br_tainted src/DocumentTemplate/tests/test_DT_Var.py::TestUrlQuoting::test_url_quoting src/DocumentTemplate/tests/test_DT_Var.py::TestUrlQuoting::test_url_quoting_plus