popen_sql.cpp (3345B)
1 #include "scraps/format/rags_sql/sql.hpp" // IWYU pragma: associated // don't crash iwyu 2 #include "scraps/format/rags_sql/popen_sql.hpp" // IWYU pragma: associated 3 4 #include <libshit/assert.hpp> 5 #include <libshit/memory_utils.hpp> 6 #include <libshit/wtf8.hpp> 7 8 #include <boost/endian/buffers.hpp> 9 10 #include <algorithm> 11 #include <sstream> 12 13 namespace Scraps::Format::RagsSql 14 { 15 16 template <typename T> 17 auto PopenSql::Read() 18 { 19 T dst; 20 p.ReadChk(&dst, sizeof(T)); 21 return dst.value(); 22 } 23 24 std::string PopenSql::ReadStr() 25 { 26 auto len = Read<U32>(); 27 std::string res(len, '\0'); 28 p.ReadChk(res.data(), len); 29 return res; 30 } 31 32 std::string PopenSql::ReadWStr() 33 { 34 auto len = Read<U32>(); 35 auto buf = Libshit::MakeUnique<char16_t[]>( 36 len / sizeof(char16_t), Libshit::uninitialized); 37 p.ReadChk(buf.get(), len); 38 return Libshit::Utf16LEToUtf8({buf.get(), len / sizeof(char16_t)}); 39 } 40 41 std::vector<Sql::Column> PopenSql::Execute(Libshit::StringView cmd) 42 { 43 { 44 auto wcmd = Libshit::Wtf8ToWtf16LE(cmd); 45 LIBSHIT_ASSERT(wcmd.size() < 0xffffffff/2); 46 47 std::uint8_t zero = 0; 48 p.WriteChk(&zero, sizeof(zero)); 49 boost::endian::little_uint32_buf_at size{ 50 static_cast<std::uint32_t>(2 * wcmd.size())}; 51 p.WriteChk(&size, sizeof(size)); 52 p.WriteChk(wcmd.data(), sizeof(char16_t) * wcmd.size()); 53 } 54 p.Flush(); 55 56 auto code = Read<U8>(); 57 if (code == 0) return {}; 58 if (code != 1) HandleError(code); 59 60 auto n_cols = Read<U32>(); 61 std::vector<Sql::Column> res; 62 res.reserve(n_cols); 63 for (std::size_t i = 0; i < n_cols; ++i) 64 { 65 auto type = Read<U32>(); 66 auto flags = Read<U32>(); 67 auto name = ReadWStr(); 68 res.push_back({ 69 static_cast<Type>(type), static_cast<ColumnFlags>(flags), name}); 70 } 71 return res; 72 } 73 74 bool PopenSql::HasRow() 75 { 76 switch (auto code = Read<U8>()) 77 { 78 case 0: return false; 79 case 1: return true; 80 default: HandleError(code); 81 } 82 } 83 84 Sql::CellInfo PopenSql::GetCellInfo() 85 { 86 auto is_blob = Read<U8>(); 87 if (is_blob >= 2) HandleError(is_blob); 88 auto status = Read<U32>(); 89 rem_size = is_blob ? CellInfo::STREAM : Read<U32>(); 90 return {static_cast<DbStatus>(status), rem_size}; 91 } 92 93 std::size_t PopenSql::GetChunk(void* buf, std::size_t len) 94 { 95 if (rem_size == CellInfo::STREAM) // BLOB chunk 96 { 97 switch (auto code = Read<U8>()) 98 { 99 case 0: 100 return 0; 101 case 1: 102 rem_size = Read<U32>(); 103 break; 104 default: 105 HandleError(code); 106 } 107 } 108 109 auto to_read = std::min<std::size_t>(len, rem_size); 110 p.ReadChk(buf, to_read); 111 if ((rem_size -= to_read) == 0) rem_size = CellInfo::STREAM; 112 return to_read; 113 } 114 115 [[noreturn]] void PopenSql::HandleError(std::uint8_t code) 116 { 117 switch (code) 118 { 119 case 0xff: // COM error 120 { 121 auto hr = Read<U32>(); 122 auto msg = ReadWStr(); 123 std::stringstream ss; 124 ss << std::hex << hr; 125 LIBSHIT_THROW(SqlError, "SQLCE COM Error", "HRESULT", ss.str(), 126 "Message", msg); 127 } 128 case 0xfe: // custom error 129 { 130 auto msg = ReadStr(); 131 LIBSHIT_THROW(SqlError, msg); 132 } 133 default: 134 LIBSHIT_THROW(SqlError, "Invalid protocol message", "Code", int(code)); 135 } 136 } 137 }