日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

【ORACLE】详解oracle数据库UTL_ENCODE包各个函数的模拟算法

發布時間:2023/12/16 数据库 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【ORACLE】详解oracle数据库UTL_ENCODE包各个函数的模拟算法 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

在前后端交互的開發中,經常會遇到需要將一些二進制數據,比如圖片文件,編碼成可打印的ascii字符進行傳遞;又者,開發人員不希望數據在傳遞中明文顯示傳遞的文本內容,并且有些字符不是ascii字符,無法很好地兼容各種環境。所以這就涉及到了各種編碼的轉換。

雖然目前各種開發語言均可處理此類編碼,但的確是存在一些場景需要在數據庫中對數據直接進行編解碼的操作。比如應用直接將編碼后的數據存到了數據庫,但如果直接在數據庫去進行查找時,發現編碼后的數據無法讀,無法寫出想要的數據的檢索條件。

在oracle數據庫中,有一個UTL_ENCODE包,里面就包含了各種編解碼的函數
官方文檔 https://docs.oracle.com/en/database/oracle/oracle-database/21/arpls/UTL_ENCODE.html

函數列表

函數列表描述
BASE64_DECODEReads the base 64-encoded RAW input string and decodes it to its original RAW value
BASE64_ENCODEEncodes the binary representation of the RAW value into base 64 elements and returns it in the form of a RAW string
MIMEHEADER_DECODEDecodes a string from mime header format
MIMEHEADER_ENCODEEncodes a string into mime header format
QUOTED_PRINTABLE_DECODEReads the varchar2 quoted printable format input string and decodes it to the corresponding RAW string
QUOTED_PRINTABLE_ENCODEReads the RAW input string and encodes it to the corresponding quoted printable format string
TEXT_DECODEDecodes a character set sensitive text string
TEXT_ENCODEEncodes a character set sensitive text string
UUDECODEReads the RAW uuencode format input string and decodes it to the corresponding RAW string
UUENCODEReads the RAW input string and encodes it to the corresponding uuencode format string

函數分類

首先,“encode"在這里意思是"編碼”,"decode"為解碼,編碼后的東西無法直接閱讀,需要解碼后才能識別。

然后來給這幾個函數劃分一下類型
從這個列表可以看出,它一共包含了5對編解碼函數,但是,實際上,關于編解碼的算法,這里只有3個,

  • BASE64
  • QUOTED_PRINTABLE
  • UUENCODE

并且這3個函數的編碼前和編碼后的數據參數類型都是二進制類型(RAW),

而另外的MIMEHEADER_ENCODE和TEXT_ENCODE這兩個函數,需要傳入一個編碼類型的參數,如下

-- Define constants for use by text_encode/decode and mimeheader_encode-- in the 'encoding' parameterbase64 CONSTANT PLS_INTEGER := 1;quoted_printable CONSTANT PLS_INTEGER := 2;

可以發現能夠通過指定編碼類型,來使用base64或者quoted_printable的編碼方式(就是上面3個編碼函數剔除了UUENCODE)。
并且MIMEHEADER_ENCODE和TEXT_ENCODE這兩個函數的編碼前和編碼后的數據參數類型都是文本(varchar2),
其中TEXT_ENCODE只會輸出編碼后的字符串本身,而MIMEHEADER_ENCODE會同時輸出字符集、編碼方式、以及編碼后的字符串。
可以看下表的對比

名稱編解碼參數類型函數名是否為編碼算法備注
BASE64rawY基于64個可打印字符來表示二進制數據
QUOTED_PRINTABLErawY可打印字符引用編碼
UUENCODErawY非標準uuencode算法,存在bug
TEXTvarchar2N可以選擇BASE64或者QUOTED_PRINTABLE兩種編碼方式之一
MIMEHEADERvarchar2N編碼結果是在TEXT編碼結果的前面加上字符集和編碼方式

下面開始逐個進行說明

BASE64

用法:

---編碼 select utl_raw.cast_to_varchar2( utl_encode.base64_encode( utl_raw.cast_to_raw('今天天氣不錯哇~'))) from dual; --輸出 '5LuK5aSp5aSp5rCU5LiN6ZSZ5ZOHfg=='--解碼 select utl_raw.cast_to_varchar2( utl_encode.base64_decode( utl_raw.cast_to_raw('5LuK5aSp5aSp5rCU5LiN6ZSZ5ZOHfg=='))) from dual; --輸出 '今天天氣不錯哇~'

BASE64太常見了,oracle/mysql/postgresql等常用數據庫和java/python等常用開發語言都有支持。BASE64的算法其實和UUENCODE重合度很高,由于后面會詳細介紹UUENCODE的算法,因此本篇暫不會詳細說明BASE64的算法了。

簡單來說,這個編碼就是將數據對應的二進制值取3個字節(即24位2進制數據),按6位作為一個新字節,得到4個新字節,然后每個字節都可以表示64以內的數值,將其對應到64個可打印字符表即可得到其對應的BASE64編碼,當然中間還是有一些換算及特殊處理,具體可參考百科
https://baike.baidu.com/item/BASE64/8545775

QUOTED_PRINTABLE

用法:

--編碼 select utl_raw.cast_to_varchar2( utl_encode.quoted_printable_encode( utl_raw.cast_to_raw('ABC567,今天天氣不錯哇~,A'))) A from dual; --輸出 'ABC567,=E4=BB=8A=E5=A4=A9=E5=A4=A9=E6=B0=94=E4=B8=8D=E9=94=99=E5=93=87~,A'--解碼 select utl_raw.cast_to_varchar2( utl_encode.quoted_printable_decode( utl_raw.cast_to_raw('ABC567,=E4=BB=8A=E5=A4=A9=E5=A4=A9=E6=B0=94=E4=B8=8D=E9=94=99=E5=93=87~,A'))) from dual; --輸出 'ABC567,今天天氣不錯哇~,A'

通過對比編碼前和編碼后可以發現,如果本身就是可打印字符,那么編碼后會保持不變;如果是不可打印字符,比如中文,那么編碼后會根據其對二進制數據的十六進制數值,使用等于號拼上字節的形式表示,
比如這里默認是UTF8編碼,“今"字的UTF8編碼的二進制數據的十六進制數值為 “E4BB8A”,然后把這三個字節的每個字節前面加上等于號,就得到了”=E4=BB=8A".

既然等于號是個關鍵的符號,那么如果原始數據里有等于號會不會導致解碼出錯呢?答案是不會,因為編碼的時候如果遇到了等于號,會轉換成"=3D"這個"3D" 其實就是等于號的十六進制ascii碼。

另外,這種編碼方式其實像極了URLENCODE,只是符號有所區別而已,然后URLENCODE還多了幾個保留符號

TEXT

用法

--編碼,指定字符集,不指定編碼方式,默認為 2,即 quoted_printable select utl_encode.text_encode('A今天天氣不錯哇~2', encode_charset => 'ZHS16GBK') from dual; --輸出 'A=BD=F1=CC=EC=CC=EC=C6=F8=B2=BB=B4=ED=CD=DB~2'--編碼,指定字符集,指定編碼方式為1,即BASE64 select utl_encode.text_encode('A今天天氣不錯哇~2', encode_charset => 'ZHS16GBK',encoding => 1) from dual; --輸出 'Qb3xzOzM7Mb4sru07c3bfjI='--解碼,針對base64字符串,指定字符集 select utl_encode.text_decode('Qb3xzOzM7Mb4sru07c3bfjI=', encode_charset => 'ZHS16GBK',encoding => 1) from dual --輸出 'A今天天氣不錯哇~2'

其實可以看到,如果是要對純文本進行編碼或者解碼,用TEXT的這種方式會比用base64或者quoted_printable本身的那兩個函數要簡單得多,不用在raw和varchar2之間轉來轉去,而且還能指定字符集,要知道對于中文來說,GBK的長度只有UTF8的三分之二。
但是,這種方式解碼的時候,必須要知道這個字符串是用什么字符集及什么方式編碼得到的,否則很可能就無法正確解碼,所以,此時可以用 MIMEHEADER 這種方式

MIMEHEADER

用法

--編碼,指定字符集,不指定編碼方式,默認為 2,即 quoted_printable select utl_encode.mimeheader_encode('A今天天氣不錯哇~2', encode_charset => 'ZHS16GBK') from dual; --輸出 '=?ZHS16GBK?Q?A=BD=F1=CC=EC=CC=EC=C6=F8=B2=BB=B4=ED=CD=DB~2?='--編碼,指定字符集,指定編碼方式為1,即BASE64 select utl_encode.mimeheader_encode('A今天天氣不錯哇~2', encode_charset => 'ZHS16GBK',encoding => 1) from dual; --輸出 '=?ZHS16GBK?B?Qb3xzOzM7Mb4sru07c3bfjI=?='--解碼, select utl_encode.mimeheader_decode('=?ZHS16GBK?Q?A=BD=F1=CC=EC=CC=EC=C6=F8=B2=BB=B4=ED=CD=DB~2?=') from dual; --輸出 'A今天天氣不錯哇~2'--解碼 select utl_encode.mimeheader_decode('=?ZHS16GBK?B?Qb3xzOzM7Mb4sru07c3bfjI=?=') from dual; --輸出 'A今天天氣不錯哇~2'

和text方式對比可以發現,mimeheader這種方式只是在text的基礎上,前后增加了一點東西而已,具體格式為

=?字符集?編碼方式?text編碼字符串?=

即等于號開始、等于號結尾,用問號把3個參數及開始結束符分隔開,其中編碼方式只有2種

B :BASE64
Q :quoted_printable

由于在編碼后輸出的字符串中已經帶上了編碼信息,因此解碼的時候就不需要再指定字符集和編碼方式了。

UUENCODE

用法:
有4個輸入參數,

  • r:需要編碼的raw值
  • type:類型(1表示頭加身體加尾巴,2表示頭加身體,3表示只要身體,4表示身體加尾巴)
  • filename:文件名
  • permission:許可
  • 其中文件名和許可都包含在輸出的頭部信息里,也就是說,只有類型為1或2時,文件名和許可才會被輸出

    --編碼,其余參數值默認 select utl_raw.cast_to_varchar2( utl_encode.uuencode( utl_raw.cast_to_raw('今天天氣真好哇~') )) from dual; --輸出 begin 0 uuencode.txt >Y+N*Y:2IY:2IYK"4YYR?Y:6]Y9.'?@ end --編碼,指定type,指定文件名,指定許可 select utl_raw.cast_to_varchar2( utl_encode.uuencode(r => utl_raw.cast_to_raw('今天天氣真好哇~'),type => 1 ,filename => 'filename.txt', permission => 777) ) from dual; --輸出 begin 777 filename.txt >Y+N*Y:2IY:2IYK"4YYR?Y:6]Y9.'?@ end

    首先要說明一下,以上執行結果是來自于oracle數據庫,但實際上這個輸出結果是錯誤的!
    將生成的這串編碼放到在線uuencode解碼的網站中去解碼的話,解碼后的數據會存在數據缺失的情況!
    因此不建議使用ORACLE數據庫中的UUENCODE編碼函數,這個函數有嚴重的BUG!
    這是Oracle中少見的持續了幾十年還不修復的BUG!

    ORACLE數據庫的utl_encode.uuencode函數,和標準的uuencode有區別,標準里規定每行60個字符,除最后一行外,應該都是大寫字母"M"開頭,但oracle中的是每行77個字符,以小寫字母"l"開頭,在mos上有記錄BUG,
    UTL_ENCODE.UUENCODE Does Not Follow The Standard Uuencode Format (文檔 ID 2197134.1)
    官方解決方案是

    Until Bug:6655881 is addressed, use a workaround or use a 3rd party external procedure based on the uuencode/uudecode standard

    簡單來說就是這個bug已經收錄,官方建議使用第三方外部過程,直到BUG修復

    但這個bug是2016年報告的呀,這都5年多了。。。不過utl_encode.uudecode倒是能解析各種各樣的長度(但是oracle解析標準的uuencode編碼會丟失數據)

    當然,uuencode這種編碼已經被base64取代了,oracle不把這個bug當回事也情有可原,畢竟這個功能開發出來幾十年了,也沒多少人反饋有問題。

    但強迫癥難忍啊。

    于是,我仔細研究uuencode的通用標準,然后在openGauss里,完整重寫了一次uuencode和uudecode函數。

    uuencode算法

    以下面這句話為例,字符集為UTF8

    今天天氣真好哇~明天天氣怎么樣呢?

    獲取其二進制數據(這里為了節省長度,以十六進制展示)

    e4bb8ae5a4a9e5a4a9e6b094e79c9fe5a5bde593877ee6988ee5a4a9e5a4a9e6b094e6808ee4b988e6a0b7e591a2

    取前3個字節

    e4 bb 8a

    其二進制表達形式為

    11100100 10111011 10001010

    然后按6位一組,前面補兩位0(可以不補,這里僅做完整字節的示意),變成4個字節

    00111001 00001011 00101110 00001010

    得到這4個字節的十進制數值為

    57 11 46 10

    這4個數字分別都加上十進制的32,得

    89 43 78 42

    對照ASCII碼表,得這4個數字分別對應的ascii字符為

    Y + N *

    這樣就處理好了前三個字節的數據。
    這里有個需要注意的地方,由于6位二進制最小可以為’000000’,其十進制為0,再加32得32,對應的ascii字符為一個空格(space),此時需要將其替換成十進制96對應的ascii字符 “`”,即常規電腦鍵盤左上角的重音符號。
    接著繼續處理后三個字節,就這么一直循環處理到最后,如果最后不足3個字節,則用二進制的"00000000"補足到3個字節來處理,因此你會發現,使用uuencode編碼的結果,后面經常會出現重音符號,這和base64編碼末尾經常出現的等于號其實是一個原因。

    所有的三個字節編碼完后,拼起來得到:

    Y+N*Y:2IY:2IYK"4YYR?Y:6]Y9.'?N:8CN6DJ>6DJ>:PE.:`CN2YB.:@M^61HN^\GP``

    接下來就要做換行處理了,標準是60個字符一行,即

    Y+N*Y:2IY:2IYK"4YYR?Y:6]Y9.'?N:8CN6DJ>6DJ>:PE.:`CN2YB.:@M^61 HN^\GP``

    然后在每行前面補一個代表長度的字符

    此時的字符,其實每4個字符來源之前的3個字節,也就是說,60個字符是對應之前的45個字節,將這個45加32,得77,取77對應的ascii字符"M",所以這個編碼的第一行前面要拼個"M",
    同理,第2行有8個字符,對應之前6個字節,6加32得38,即 “&” (其實這里有個問題,有些算法會算出來這里為36,即"$",但這一般不會影響解碼結果,因為大部分解碼程序都不會去校驗這個長度,所以這也是這種編碼的一個槽點),

    MY+N*Y:2IY:2IYK"4YYR?Y:6]Y9.'?N:8CN6DJ>6DJ>:PE.:`CN2YB.:@M^61 $HN^\GP``

    然后換一行,加一個重音符號

    MY+N*Y:2IY:2IYK"4YYR?Y:6]Y9.'?N:8CN6DJ>6DJ>:PE.:`CN2YB.:@M^61 $HN^\GP`` `

    到此,主體部分編碼就完成了,然后根據需要,在前面或者后面加上頭部信息及尾部信息

    begin 0 uuencode.txt MY+N*Y:2IY:2IYK"4YYR?Y:6]Y9.'?N:8CN6DJ>6DJ>:PE.:`CN2YB.:@M^61 $HN^\GP`` ` end

    這樣就完成了。

    可以看到,一般情況下,如果生成多行主體,那么除去最后一行外的每一行,第一個字符都應該為大寫的"M"(ascii碼77),而ORACLE為小寫的"l",很可能是因為ORACLE開發人員自作聰明,以為是每行放77個字符,減去表示長度的這個字符,還剩76個字符,然后76加32得108,108對應的ascii字符就是小寫的英文字母"l"!

    從整個編碼過程來看,中間的3字節轉4字節是相當巧妙,但是那個分行加長度,以及空格替換成重音符號又是相當坑,而且這64個字符中是可能出現其他開發語言的保留符號的,傳輸過程中還得進行轉義,所以這種編碼方式已經被base64取代了。

    base64與uuencode的不同點是,uuencode是直接映射到ascii碼表,而base64則是單獨定義了要映射到哪64個字符,并且base64沒有這么坑爹的要加行長度。

    下面是兩種編碼方式的映射字符表,可以看到都是64個字符,但BASE64少了很多奇奇怪怪的符號

    base64 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ uuencode `!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_

    因此,如果懂了UUENCODE是如何編碼的,那么同理也可以寫出BASE64的編碼程序

    總結

    這篇雖然不及上一篇關于UTL_RAW的更接近底層邏輯,但是這篇更接近開發的實際使用場景,讓大家知道其實數據庫里也是可以做一些復雜的事情的。

    另外,這篇文章也算是我在寫完openGauss的UTL_ENCODE兼容包的一個總結吧,兼容代碼可以在我的項目或者compat_tools項目中找到
    utl_encode.sql
    compat-tools

    • 本文作者: DarkAthena
    • 本文鏈接: https://www.darkathena.top/archives/about-utl-encode-and-emulate-cal
    • 版權聲明: 本博客所有文章除特別聲明外,均采用CC BY-NC-SA 3.0 許可協議。轉載請注明出處!

    總結

    以上是生活随笔為你收集整理的【ORACLE】详解oracle数据库UTL_ENCODE包各个函数的模拟算法的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。