encodeJsonObject encodes the specified |jsonObject| into MySQL's internal JSON encoding and returns the type ID indicating whether this is a small or large object, the encoded object data, and any error encountered. The |largeEncoding| param controls whether this function will use the small object e
(jsonObject map[string]any, largeEncoding bool)
| 146 | // - Value Entries (variable): 1 per value; 1 byte for type ID, 2 bytes for offset or inlined literal value for jsonTypeSmallObject, otherwise 4 |
| 147 | // - Keys (variable): 1 per key; encoded string bytes |
| 148 | // - Values (variable): 1 per value; encoded value bytes |
| 149 | func encodeJsonObject(jsonObject map[string]any, largeEncoding bool) (typeId byte, encodedObject []byte, err error) { |
| 150 | var keyEntriesBuffer []byte |
| 151 | var keysBuffer []byte |
| 152 | nextKeysOffset := calculateInitialObjectKeysOffset(len(jsonObject), largeEncoding) |
| 153 | |
| 154 | // Sort the keys so that we can process the keys and values in a consistent order. MySQL seems to sort |
| 155 | // json keys internally first by length, then alphabetically, but correct replication doesn't seem to |
| 156 | // rely on matching that behavior. |
| 157 | sortedKeys := make([]string, 0, len(jsonObject)) |
| 158 | for key := range jsonObject { |
| 159 | sortedKeys = append(sortedKeys, key) |
| 160 | } |
| 161 | sort.Strings(sortedKeys) |
| 162 | |
| 163 | // Process keys first, since value entry data depends on offsets that we don't know until we |
| 164 | // process all the keys. |
| 165 | for _, key := range sortedKeys { |
| 166 | // NOTE: Don't use encodeJsonValue for the key – its length gets encoded slightly differently |
| 167 | // for JSON objects. |
| 168 | encodedValue := []byte(key) |
| 169 | |
| 170 | if !largeEncoding && nextKeysOffset > maxOffsetSize-uint32(len(encodedValue)) { |
| 171 | return 0, nil, fmt.Errorf("offset too large for small object encoding") |
| 172 | } |
| 173 | |
| 174 | keyEntriesBuffer = appendForEncoding(keyEntriesBuffer, nextKeysOffset, largeEncoding) |
| 175 | keyEntriesBuffer = append(keyEntriesBuffer, byte(len(encodedValue)), byte(len(encodedValue)<<8)) |
| 176 | keysBuffer = append(keysBuffer, encodedValue...) |
| 177 | nextKeysOffset += uint32(len(encodedValue)) |
| 178 | } |
| 179 | |
| 180 | // Process values – since the object values are written after the keys, and we need to store the |
| 181 | // offsets to those locations in the value entries that appear before the keys and the values, we |
| 182 | // have to make a second pass through the object to process the values once we know the final |
| 183 | // length of the keys section. |
| 184 | var valueEntriesBuffer []byte |
| 185 | var valuesBuffer []byte |
| 186 | nextValuesOffset := nextKeysOffset |
| 187 | for _, key := range sortedKeys { |
| 188 | value := jsonObject[key] |
| 189 | typeId, encodedValue, err := encodeJsonValue(value) |
| 190 | if err != nil { |
| 191 | return 0, nil, err |
| 192 | } |
| 193 | |
| 194 | // Literals may be inlined in the value-entries section |
| 195 | if typeId == jsonTypeLiteral { |
| 196 | valueEntriesBuffer = append(valueEntriesBuffer, typeId) |
| 197 | if len(encodedValue) != 1 { |
| 198 | return 0, nil, fmt.Errorf("unexpected buffer length") |
| 199 | } |
| 200 | valueEntriesBuffer = appendForEncoding(valueEntriesBuffer, uint32(encodedValue[0]), largeEncoding) |
| 201 | } else { |
| 202 | if !largeEncoding && nextValuesOffset > maxOffsetSize-uint32(len(encodedValue)) { |
| 203 | return 0, nil, fmt.Errorf("offset too large for small object encoding") |
| 204 | } |
| 205 |
no test coverage detected