|
|
|
@ -1,5 +1,6 @@
|
|
|
|
|
#![feature(const_in_array_repeat_expressions)]
|
|
|
|
|
#![feature(const_fn)]
|
|
|
|
|
#![feature(drain_filter)]
|
|
|
|
|
#![cfg_attr(nightly, feature(test))]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -9,11 +10,17 @@
|
|
|
|
|
|
|
|
|
|
const MAX: usize = 256;
|
|
|
|
|
|
|
|
|
|
//TODO: Move test
|
|
|
|
|
//TODO: Document
|
|
|
|
|
//TODO: Readme
|
|
|
|
|
//TODO: LICENSE
|
|
|
|
|
//TODO: Publish and upload to githubxc
|
|
|
|
|
|
|
|
|
|
use std::{
|
|
|
|
|
borrow::Borrow,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub trait Collapsible: Eq
|
|
|
|
|
pub trait Collapse: Eq
|
|
|
|
|
{
|
|
|
|
|
fn collapse(&self) -> u8;
|
|
|
|
|
}
|
|
|
|
@ -23,7 +30,7 @@ pub trait Collapsible: Eq
|
|
|
|
|
pub struct Page<TKey,TValue>([Option<(TKey, TValue)>; MAX]);
|
|
|
|
|
|
|
|
|
|
impl<K,V> Page<K,V>
|
|
|
|
|
where K: Collapsible
|
|
|
|
|
where K: Collapse
|
|
|
|
|
{
|
|
|
|
|
/// Create a new blank page
|
|
|
|
|
pub const fn new() -> Self
|
|
|
|
@ -47,12 +54,12 @@ where K: Collapsible
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn search<Q: ?Sized>(&self, key: &Q) -> &Option<(K,V)>
|
|
|
|
|
where Q: Collapsible
|
|
|
|
|
where Q: Collapse
|
|
|
|
|
{
|
|
|
|
|
&self.0[usize::from(key.collapse())]
|
|
|
|
|
}
|
|
|
|
|
fn search_mut<Q: ?Sized>(&mut self, key: &Q) -> &mut Option<(K,V)>
|
|
|
|
|
where Q: Collapsible
|
|
|
|
|
where Q: Collapse
|
|
|
|
|
{
|
|
|
|
|
&mut self.0[usize::from(key.collapse())]
|
|
|
|
|
}
|
|
|
|
@ -64,7 +71,7 @@ where K: Collapsible
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<K,V> IntoIterator for Page<K,V>
|
|
|
|
|
where K: Collapsible
|
|
|
|
|
where K: Collapse
|
|
|
|
|
{
|
|
|
|
|
type Item= (K,V);
|
|
|
|
|
type IntoIter = IntoPageElements<K,V>;
|
|
|
|
@ -77,7 +84,7 @@ where K: Collapsible
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl<K,V> Default for Page<K,V>
|
|
|
|
|
where K: Collapsible
|
|
|
|
|
where K: Collapse
|
|
|
|
|
{
|
|
|
|
|
#[inline]
|
|
|
|
|
fn default() -> Self
|
|
|
|
@ -91,10 +98,50 @@ pub struct Map<TKey, TValue>(Vec<Page<TKey,TValue>>);
|
|
|
|
|
|
|
|
|
|
pub mod iter;
|
|
|
|
|
use iter::*;
|
|
|
|
|
pub mod entry;
|
|
|
|
|
pub use entry::Entry;
|
|
|
|
|
|
|
|
|
|
impl<K,V> Map<K,V>
|
|
|
|
|
where K: Collapsible
|
|
|
|
|
where K: Collapse
|
|
|
|
|
{
|
|
|
|
|
fn new_page(&mut self) -> &mut Page<K,V>
|
|
|
|
|
{
|
|
|
|
|
let len = self.0.len();
|
|
|
|
|
self.0.push(Page::new());
|
|
|
|
|
&mut self.0[len]
|
|
|
|
|
}
|
|
|
|
|
#[inline(always)] fn fuck_entry(&mut self, key: K) -> Option<Entry<'_, K, V>>
|
|
|
|
|
{
|
|
|
|
|
for page in self.0.iter_mut()
|
|
|
|
|
{
|
|
|
|
|
let re = page.search_mut(&key);
|
|
|
|
|
match re {
|
|
|
|
|
Some((ref ok, _)) if key.eq(ok.borrow()) => {
|
|
|
|
|
return Some(Entry::Occupied(entry::OccupiedEntry(re)));
|
|
|
|
|
},
|
|
|
|
|
None => {
|
|
|
|
|
return Some(Entry::Vacant(entry::VacantEntry(re, key)));
|
|
|
|
|
},
|
|
|
|
|
_ => (),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
pub fn entry(&mut self, key: K) -> Entry<'_, K, V>
|
|
|
|
|
{
|
|
|
|
|
if self.0.iter()
|
|
|
|
|
.filter(|x| x.search(&key).is_none())
|
|
|
|
|
.count() == 0 {
|
|
|
|
|
self.new_page();
|
|
|
|
|
}//so dumb..... SO dumb
|
|
|
|
|
//will need to completely reimplement all entry::* shit to just have mut reference to Map and then usize indecies for location I guess. Fuck this
|
|
|
|
|
self.fuck_entry(key).unwrap()
|
|
|
|
|
}
|
|
|
|
|
pub fn clean(&mut self)
|
|
|
|
|
{
|
|
|
|
|
self.0.drain_filter(|x| x.len() <1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn len(&self) -> usize
|
|
|
|
|
{
|
|
|
|
|
self.pages().map(Page::len).sum()
|
|
|
|
@ -151,7 +198,7 @@ where K: Collapsible
|
|
|
|
|
|
|
|
|
|
pub fn get_mut<Q: ?Sized>(&mut self, key: &Q) -> Option<&mut V>
|
|
|
|
|
where K: Borrow<Q>,
|
|
|
|
|
Q: Collapsible + Eq
|
|
|
|
|
Q: Collapse + Eq
|
|
|
|
|
{
|
|
|
|
|
for page in self.0.iter_mut()
|
|
|
|
|
{
|
|
|
|
@ -167,14 +214,14 @@ where K: Collapsible
|
|
|
|
|
|
|
|
|
|
#[inline] pub fn contains_key<Q: ?Sized>(&self, key: &Q) -> bool
|
|
|
|
|
where K: Borrow<Q>,
|
|
|
|
|
Q: Collapsible + Eq
|
|
|
|
|
Q: Collapse + Eq
|
|
|
|
|
{
|
|
|
|
|
self.get(key).is_some()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get<Q: ?Sized>(&self, key: &Q) -> Option<&V>
|
|
|
|
|
where K: Borrow<Q>,
|
|
|
|
|
Q: Collapsible + Eq
|
|
|
|
|
Q: Collapse + Eq
|
|
|
|
|
{
|
|
|
|
|
for page in self.0.iter()
|
|
|
|
|
{
|
|
|
|
@ -187,11 +234,28 @@ where K: Collapsible
|
|
|
|
|
}
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn search_mut<Q: ?Sized>(&mut self, key: &Q) -> Option<&mut Option<(K,V)>>
|
|
|
|
|
where K: Borrow<Q>,
|
|
|
|
|
Q: Collapse + Eq
|
|
|
|
|
{
|
|
|
|
|
for page in self.0.iter_mut()
|
|
|
|
|
{
|
|
|
|
|
let se = page.search_mut(key);
|
|
|
|
|
match se {
|
|
|
|
|
Some((ref ok, _)) if key.eq(ok.borrow()) => {
|
|
|
|
|
return Some(se);
|
|
|
|
|
},
|
|
|
|
|
_ => (),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn remove<Q: ?Sized>(&mut self, key: &Q) -> Option<V>
|
|
|
|
|
where K: Borrow<Q>,
|
|
|
|
|
Q: Collapsible + Eq
|
|
|
|
|
Q: Collapse + Eq
|
|
|
|
|
{
|
|
|
|
|
for page in self.0.iter_mut()
|
|
|
|
|
{
|
|
|
|
@ -229,7 +293,7 @@ where K: Collapsible
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<K: Collapsible, V> IntoIterator for Map<K,V>
|
|
|
|
|
impl<K: Collapse, V> IntoIterator for Map<K,V>
|
|
|
|
|
{
|
|
|
|
|
type Item= (K,V);
|
|
|
|
|
type IntoIter = IntoIter<K,V>;
|
|
|
|
@ -245,7 +309,7 @@ pub trait CollapseMemory: Eq
|
|
|
|
|
{
|
|
|
|
|
fn as_memory(&self) -> &[u8];
|
|
|
|
|
}
|
|
|
|
|
impl<T> Collapsible for T
|
|
|
|
|
impl<T> Collapse for T
|
|
|
|
|
where T: CollapseMemory
|
|
|
|
|
{
|
|
|
|
|
fn collapse(&self) -> u8 {
|
|
|
|
@ -267,466 +331,4 @@ pub fn collapse<T: AsRef<[u8]>>(bytes: T) -> u8
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests
|
|
|
|
|
{
|
|
|
|
|
use super::*;
|
|
|
|
|
#[cfg(nightly)] use test::{Bencher, black_box};
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn it_works()
|
|
|
|
|
{
|
|
|
|
|
let mut map = Map::new();
|
|
|
|
|
|
|
|
|
|
map.insert('>', false);
|
|
|
|
|
map.insert('<', true);
|
|
|
|
|
map.insert('ł', true);
|
|
|
|
|
|
|
|
|
|
let clone = map.clone();
|
|
|
|
|
|
|
|
|
|
for (k, v) in clone.into_iter()
|
|
|
|
|
{
|
|
|
|
|
assert!(map.get(&k) == Some(&v))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn hmap_comp()
|
|
|
|
|
{
|
|
|
|
|
let mut small = Map::new();
|
|
|
|
|
let mut hash = HashMap::new();
|
|
|
|
|
|
|
|
|
|
let input = r#"こんばんわBludgeoning clsql and mariadb until they kind of work
|
|
|
|
|
|
|
|
|
|
There are no good mysql connectors for Common Lisp, whatever you decide to use, you're not going to have a good time. clsql is pretty much the best we've got, and since I had such a delightful experience configuring it myself, I hope to smooth the process of getting this shitware up and running with my own little blogpost here.
|
|
|
|
|
connecting to a database
|
|
|
|
|
|
|
|
|
|
Connecting to mysql with clsql looks something like this
|
|
|
|
|
|
|
|
|
|
(clsql:connect
|
|
|
|
|
'("host" "database" "username" "password" &optional "port") ;; connection-spec
|
|
|
|
|
:database-type :mysql) ;; Defaults to clsql:*default-database-type*
|
|
|
|
|
|
|
|
|
|
clsql builds ffi libraries the first time you try to connect to a database, and you can get some pretty arcane error messages if you don't have the right shared libraries installed already. These are (hopefully) packaged by your distro already, in Gentoo you want
|
|
|
|
|
|
|
|
|
|
$ sudo emerge --ask dev-db/mariadb-connector-c
|
|
|
|
|
|
|
|
|
|
in openSUSE it's
|
|
|
|
|
|
|
|
|
|
$ zypper in mariadb-connector-odbc
|
|
|
|
|
|
|
|
|
|
if you're on Debian or Ubuntu then God save your soul, etc.
|
|
|
|
|
|
|
|
|
|
Despite mariadb being a drop-in replacement for mysql, the clsql developers have made little to no effort to actually support it, to the point where mariadb's versioning system isn't even accounted for when loading the libraries, causing clsql to just error out for absolutely no reason. (actually, this shitware doesn't even 'support' modern versions on mysql because the version numbers have increased above what they check for, but they load fine as well)
|
|
|
|
|
|
|
|
|
|
We can modify the version checking script, to fix this, which you can find here: ~/quicklisp/dists/quicklisp/software/clsql-*-git/db-mysql/mysql-client-info.lisp (If your quicklisp is installed in its default directory you can paste this straight into a terminal or VIM or some such and it'll expand itself, which is kinda neat.)
|
|
|
|
|
|
|
|
|
|
If we add the top three lines just before the bottom two it'll stopping giving you stupid error messages when you try and connect.
|
|
|
|
|
|
|
|
|
|
((and (eql (schar *mysql-client-info* 0) #\1)
|
|
|
|
|
(eql (schar *mysql-client-info* 0) #\0))
|
|
|
|
|
(pushnew :mysql-client-v6 cl:*features*))
|
|
|
|
|
(t
|
|
|
|
|
(error "Unknown mysql client version '~A'." *mysql-client-info*)))))
|
|
|
|
|
|
|
|
|
|
charset issues
|
|
|
|
|
|
|
|
|
|
After (finally) connecting, when we first query the database we might get an error like this:
|
|
|
|
|
|
|
|
|
|
debugger invoked on a BABEL-ENCODINGS:INVALID-UTF8-STARTER-BYTE in thread
|
|
|
|
|
#<THREAD "main thread" RUNNING {B3E2151}>: Illegal :UTF-8 character starting at position 18.
|
|
|
|
|
|
|
|
|
|
We can identify the issue like so:
|
|
|
|
|
|
|
|
|
|
(defvar charsets
|
|
|
|
|
"SELECT VARIABLE_NAME, SESSION_VALUE \
|
|
|
|
|
FROM INFORMATION_SCHEMA.SYSTEM_VARIABLES \
|
|
|
|
|
WHERE VARIABLE_NAME LIKE 'character_set_c%' \
|
|
|
|
|
OR VARIABLE_NAME LIKE 'character_set_re%' \
|
|
|
|
|
OR VARIABLE_NAME LIKE 'collation_c%';")
|
|
|
|
|
|
|
|
|
|
;; CHARSETS
|
|
|
|
|
(clsql:query charsets)
|
|
|
|
|
|
|
|
|
|
;; (("COLLATION_CONNECTION" "latin1_swedish_ci")
|
|
|
|
|
;; ("CHARACTER_SET_CONNECTION" "latin1")
|
|
|
|
|
;; ("CHARACTER_SET_RESULTS" "latin1")
|
|
|
|
|
;; ("CHARACTER_SET_CLIENT" "latin1"))
|
|
|
|
|
;; ("VARIABLE_NAME" "SESSION_VALUE")
|
|
|
|
|
|
|
|
|
|
These are actually the default collation and character sets in MYSQL; Smarter mysql drivers would set their charset and collation to utf8 by themseleves, but not clsql! (the collation is set to latin1_swedish_ci by default because David Axmark, one of the co-founders of MYSQL, is Swedish. No, really.)
|
|
|
|
|
|
|
|
|
|
We can fix this on the database connection level by setting names:
|
|
|
|
|
|
|
|
|
|
(clsql:execute-command "SET NAMES 'utf8mb4'")
|
|
|
|
|
|
|
|
|
|
And now when we query charsets again they're utf8!
|
|
|
|
|
|
|
|
|
|
(clsql:query charsets)
|
|
|
|
|
|
|
|
|
|
;; (("COLLATION_CONNECTION" "utf8mb4_general_ci")
|
|
|
|
|
;; ("CHARACTER_SET_CONNECTION" "utf8mb4")
|
|
|
|
|
;; ("CHARACTER_SET_RESULTS" "utf8mb4")
|
|
|
|
|
;; ("CHARACTER_SET_CLIENT" "utf8mb4"))
|
|
|
|
|
;; ("VARIABLE_NAME" "SESSION_VALUE")
|
|
|
|
|
|
|
|
|
|
However, this has to be set per-database connection. Really, we want to be using utf8 by default, because it's fucking 2020 and utf8 has been the standard character encoding for over a decade.
|
|
|
|
|
|
|
|
|
|
You can find you mariadb config files with this shell command:
|
|
|
|
|
|
|
|
|
|
$ mysql --help --verbose | grep -A 1 "Default options"
|
|
|
|
|
|
|
|
|
|
Default options are read from the following files in the given order:
|
|
|
|
|
/etc/my.cnf /etc/mysql/my.cnf ~/.my.cnf
|
|
|
|
|
|
|
|
|
|
... then add add these values under their respective headings in one of those files and hey presto, you're charsetting like it's 2008!
|
|
|
|
|
|
|
|
|
|
[client]
|
|
|
|
|
default-character-set = utf8mb4
|
|
|
|
|
|
|
|
|
|
[mysql]
|
|
|
|
|
default-character-set = utf8mb4
|
|
|
|
|
|
|
|
|
|
[mysqld]
|
|
|
|
|
collation-server = utf8mb4_unicode_ci
|
|
|
|
|
init-connect= 'SET NAMES utf8mb4'
|
|
|
|
|
character-set-server = utf8mb4
|
|
|
|
|
|
|
|
|
|
Now the charset for our initial connection should be set to utf8, and we're free to hack away~ "#.chars();
|
|
|
|
|
|
|
|
|
|
for ch in input
|
|
|
|
|
{
|
|
|
|
|
if !small.contains_key(&ch) {
|
|
|
|
|
small.insert(ch, 0);
|
|
|
|
|
} else {
|
|
|
|
|
*small.get_mut(&ch).unwrap() += 1;
|
|
|
|
|
}
|
|
|
|
|
if !hash.contains_key(&ch) {
|
|
|
|
|
hash.insert(ch, 0);
|
|
|
|
|
} else {
|
|
|
|
|
*hash.get_mut(&ch).unwrap() += 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut op1: Vec<_> = small.into_iter().collect();
|
|
|
|
|
let mut op2: Vec<_> = hash.into_iter().collect();
|
|
|
|
|
|
|
|
|
|
op1.sort();
|
|
|
|
|
op2.sort();
|
|
|
|
|
|
|
|
|
|
assert_eq!(&op1[..], &op2[..]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(nightly)]
|
|
|
|
|
#[bench]
|
|
|
|
|
fn char_smallmap(b: &mut Bencher)
|
|
|
|
|
{
|
|
|
|
|
let mut map =Map::new();
|
|
|
|
|
|
|
|
|
|
let nm: Vec<char> = r#"こんばんわ
|
|
|
|
|
|
|
|
|
|
E メール: boku (at) plum (dot) moe (PGP Key)
|
|
|
|
|
|
|
|
|
|
bantflags
|
|
|
|
|
|
|
|
|
|
words
|
|
|
|
|
|
|
|
|
|
stream"#.chars().collect();
|
|
|
|
|
let mut i=0;
|
|
|
|
|
b.iter(|| {
|
|
|
|
|
black_box(map.insert(nm[i], 100usize));
|
|
|
|
|
if i == nm.len()-1 {
|
|
|
|
|
i =0
|
|
|
|
|
} else {
|
|
|
|
|
i+=1;
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
#[cfg(nightly)]
|
|
|
|
|
#[bench]
|
|
|
|
|
fn u8_smallmap(b: &mut Bencher)
|
|
|
|
|
{
|
|
|
|
|
let mut map =Map::new();
|
|
|
|
|
|
|
|
|
|
let mut i=0u8;
|
|
|
|
|
b.iter(|| {
|
|
|
|
|
black_box(map.insert(i, 100usize));
|
|
|
|
|
if i == 255 {
|
|
|
|
|
i=0
|
|
|
|
|
} else {
|
|
|
|
|
i+=1;
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
|
|
|
|
|
#[cfg(nightly)]
|
|
|
|
|
#[bench]
|
|
|
|
|
fn char_map(b: &mut Bencher)
|
|
|
|
|
{
|
|
|
|
|
let mut map =HashMap::new();
|
|
|
|
|
|
|
|
|
|
let nm: Vec<char> = r#"こんばんわ
|
|
|
|
|
|
|
|
|
|
E メール: boku (at) plum (dot) moe (PGP Key)
|
|
|
|
|
|
|
|
|
|
bantflags
|
|
|
|
|
|
|
|
|
|
words
|
|
|
|
|
|
|
|
|
|
stream"#.chars().collect();
|
|
|
|
|
let mut i=0;
|
|
|
|
|
b.iter(|| {
|
|
|
|
|
black_box(map.insert(nm[i], 100usize));
|
|
|
|
|
if i == nm.len()-1 {
|
|
|
|
|
i =0
|
|
|
|
|
} else {
|
|
|
|
|
i+=1;
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(nightly)]
|
|
|
|
|
#[bench]
|
|
|
|
|
fn u8_map(b: &mut Bencher)
|
|
|
|
|
{
|
|
|
|
|
let mut map =HashMap::new();
|
|
|
|
|
|
|
|
|
|
let mut i=0u8;
|
|
|
|
|
b.iter(|| {
|
|
|
|
|
black_box(map.insert(i, 100usize));
|
|
|
|
|
if i == 255 {
|
|
|
|
|
i=0
|
|
|
|
|
} else {
|
|
|
|
|
i+=1;
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
#[cfg(nightly)]
|
|
|
|
|
#[bench]
|
|
|
|
|
fn smap_bench(b: &mut Bencher)
|
|
|
|
|
{
|
|
|
|
|
let mut small = Map::new();
|
|
|
|
|
|
|
|
|
|
let input = r#"こんばんわBludgeoning clsql and mariadb until they kind of work
|
|
|
|
|
|
|
|
|
|
There are no good mysql connectors for Common Lisp, whatever you decide to use, you're not going to have a good time. clsql is pretty much the best we've got, and since I had such a delightful experience configuring it myself, I hope to smooth the process of getting this shitware up and running with my own little blogpost here.
|
|
|
|
|
connecting to a database
|
|
|
|
|
|
|
|
|
|
Connecting to mysql with clsql looks something like this
|
|
|
|
|
|
|
|
|
|
(clsql:connect
|
|
|
|
|
'("host" "database" "username" "password" &optional "port") ;; connection-spec
|
|
|
|
|
:database-type :mysql) ;; Defaults to clsql:*default-database-type*
|
|
|
|
|
|
|
|
|
|
clsql builds ffi libraries the first time you try to connect to a database, and you can get some pretty arcane error messages if you don't have the right shared libraries installed already. These are (hopefully) packaged by your distro already, in Gentoo you want
|
|
|
|
|
|
|
|
|
|
$ sudo emerge --ask dev-db/mariadb-connector-c
|
|
|
|
|
|
|
|
|
|
in openSUSE it's
|
|
|
|
|
|
|
|
|
|
$ zypper in mariadb-connector-odbc
|
|
|
|
|
|
|
|
|
|
if you're on Debian or Ubuntu then God save your soul, etc.
|
|
|
|
|
|
|
|
|
|
Despite mariadb being a drop-in replacement for mysql, the clsql developers have made little to no effort to actually support it, to the point where mariadb's versioning system isn't even accounted for when loading the libraries, causing clsql to just error out for absolutely no reason. (actually, this shitware doesn't even 'support' modern versions on mysql because the version numbers have increased above what they check for, but they load fine as well)
|
|
|
|
|
|
|
|
|
|
We can modify the version checking script, to fix this, which you can find here: ~/quicklisp/dists/quicklisp/software/clsql-*-git/db-mysql/mysql-client-info.lisp (If your quicklisp is installed in its default directory you can paste this straight into a terminal or VIM or some such and it'll expand itself, which is kinda neat.)
|
|
|
|
|
|
|
|
|
|
If we add the top three lines just before the bottom two it'll stopping giving you stupid error messages when you try and connect.
|
|
|
|
|
|
|
|
|
|
((and (eql (schar *mysql-client-info* 0) #\1)
|
|
|
|
|
(eql (schar *mysql-client-info* 0) #\0))
|
|
|
|
|
(pushnew :mysql-client-v6 cl:*features*))
|
|
|
|
|
(t
|
|
|
|
|
(error "Unknown mysql client version '~A'." *mysql-client-info*)))))
|
|
|
|
|
|
|
|
|
|
charset issues
|
|
|
|
|
|
|
|
|
|
After (finally) connecting, when we first query the database we might get an error like this:
|
|
|
|
|
|
|
|
|
|
debugger invoked on a BABEL-ENCODINGS:INVALID-UTF8-STARTER-BYTE in thread
|
|
|
|
|
#<THREAD "main thread" RUNNING {B3E2151}>: Illegal :UTF-8 character starting at position 18.
|
|
|
|
|
|
|
|
|
|
We can identify the issue like so:
|
|
|
|
|
|
|
|
|
|
(defvar charsets
|
|
|
|
|
"SELECT VARIABLE_NAME, SESSION_VALUE \
|
|
|
|
|
FROM INFORMATION_SCHEMA.SYSTEM_VARIABLES \
|
|
|
|
|
WHERE VARIABLE_NAME LIKE 'character_set_c%' \
|
|
|
|
|
OR VARIABLE_NAME LIKE 'character_set_re%' \
|
|
|
|
|
OR VARIABLE_NAME LIKE 'collation_c%';")
|
|
|
|
|
|
|
|
|
|
;; CHARSETS
|
|
|
|
|
(clsql:query charsets)
|
|
|
|
|
|
|
|
|
|
;; (("COLLATION_CONNECTION" "latin1_swedish_ci")
|
|
|
|
|
;; ("CHARACTER_SET_CONNECTION" "latin1")
|
|
|
|
|
;; ("CHARACTER_SET_RESULTS" "latin1")
|
|
|
|
|
;; ("CHARACTER_SET_CLIENT" "latin1"))
|
|
|
|
|
;; ("VARIABLE_NAME" "SESSION_VALUE")
|
|
|
|
|
|
|
|
|
|
These are actually the default collation and character sets in MYSQL; Smarter mysql drivers would set their charset and collation to utf8 by themseleves, but not clsql! (the collation is set to latin1_swedish_ci by default because David Axmark, one of the co-founders of MYSQL, is Swedish. No, really.)
|
|
|
|
|
|
|
|
|
|
We can fix this on the database connection level by setting names:
|
|
|
|
|
|
|
|
|
|
(clsql:execute-command "SET NAMES 'utf8mb4'")
|
|
|
|
|
|
|
|
|
|
And now when we query charsets again they're utf8!
|
|
|
|
|
|
|
|
|
|
(clsql:query charsets)
|
|
|
|
|
|
|
|
|
|
;; (("COLLATION_CONNECTION" "utf8mb4_general_ci")
|
|
|
|
|
;; ("CHARACTER_SET_CONNECTION" "utf8mb4")
|
|
|
|
|
;; ("CHARACTER_SET_RESULTS" "utf8mb4")
|
|
|
|
|
;; ("CHARACTER_SET_CLIENT" "utf8mb4"))
|
|
|
|
|
;; ("VARIABLE_NAME" "SESSION_VALUE")
|
|
|
|
|
|
|
|
|
|
However, this has to be set per-database connection. Really, we want to be using utf8 by default, because it's fucking 2020 and utf8 has been the standard character encoding for over a decade.
|
|
|
|
|
|
|
|
|
|
You can find you mariadb config files with this shell command:
|
|
|
|
|
|
|
|
|
|
$ mysql --help --verbose | grep -A 1 "Default options"
|
|
|
|
|
|
|
|
|
|
Default options are read from the following files in the given order:
|
|
|
|
|
/etc/my.cnf /etc/mysql/my.cnf ~/.my.cnf
|
|
|
|
|
|
|
|
|
|
... then add add these values under their respective headings in one of those files and hey presto, you're charsetting like it's 2008!
|
|
|
|
|
|
|
|
|
|
[client]
|
|
|
|
|
default-character-set = utf8mb4
|
|
|
|
|
|
|
|
|
|
[mysql]
|
|
|
|
|
default-character-set = utf8mb4
|
|
|
|
|
|
|
|
|
|
[mysqld]
|
|
|
|
|
collation-server = utf8mb4_unicode_ci
|
|
|
|
|
init-connect= 'SET NAMES utf8mb4'
|
|
|
|
|
character-set-server = utf8mb4
|
|
|
|
|
|
|
|
|
|
Now the charset for our initial connection should be set to utf8, and we're free to hack away~ "#;
|
|
|
|
|
|
|
|
|
|
b.iter(|| {
|
|
|
|
|
for ch in input.chars()
|
|
|
|
|
{
|
|
|
|
|
if !small.contains_key(&ch) {
|
|
|
|
|
small.insert(ch, 0);
|
|
|
|
|
} else {
|
|
|
|
|
*small.get_mut(&ch).unwrap() += 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
#[cfg(nightly)]
|
|
|
|
|
#[bench]
|
|
|
|
|
fn hmap_bench(b: &mut Bencher)
|
|
|
|
|
{
|
|
|
|
|
let mut small = HashMap::new();
|
|
|
|
|
|
|
|
|
|
let input = r#"こんばんわBludgeoning clsql and mariadb until they kind of work
|
|
|
|
|
|
|
|
|
|
There are no good mysql connectors for Common Lisp, whatever you decide to use, you're not going to have a good time. clsql is pretty much the best we've got, and since I had such a delightful experience configuring it myself, I hope to smooth the process of getting this shitware up and running with my own little blogpost here.
|
|
|
|
|
connecting to a database
|
|
|
|
|
|
|
|
|
|
Connecting to mysql with clsql looks something like this
|
|
|
|
|
|
|
|
|
|
(clsql:connect
|
|
|
|
|
'("host" "database" "username" "password" &optional "port") ;; connection-spec
|
|
|
|
|
:database-type :mysql) ;; Defaults to clsql:*default-database-type*
|
|
|
|
|
|
|
|
|
|
clsql builds ffi libraries the first time you try to connect to a database, and you can get some pretty arcane error messages if you don't have the right shared libraries installed already. These are (hopefully) packaged by your distro already, in Gentoo you want
|
|
|
|
|
|
|
|
|
|
$ sudo emerge --ask dev-db/mariadb-connector-c
|
|
|
|
|
|
|
|
|
|
in openSUSE it's
|
|
|
|
|
|
|
|
|
|
$ zypper in mariadb-connector-odbc
|
|
|
|
|
|
|
|
|
|
if you're on Debian or Ubuntu then God save your soul, etc.
|
|
|
|
|
|
|
|
|
|
Despite mariadb being a drop-in replacement for mysql, the clsql developers have made little to no effort to actually support it, to the point where mariadb's versioning system isn't even accounted for when loading the libraries, causing clsql to just error out for absolutely no reason. (actually, this shitware doesn't even 'support' modern versions on mysql because the version numbers have increased above what they check for, but they load fine as well)
|
|
|
|
|
|
|
|
|
|
We can modify the version checking script, to fix this, which you can find here: ~/quicklisp/dists/quicklisp/software/clsql-*-git/db-mysql/mysql-client-info.lisp (If your quicklisp is installed in its default directory you can paste this straight into a terminal or VIM or some such and it'll expand itself, which is kinda neat.)
|
|
|
|
|
|
|
|
|
|
If we add the top three lines just before the bottom two it'll stopping giving you stupid error messages when you try and connect.
|
|
|
|
|
|
|
|
|
|
((and (eql (schar *mysql-client-info* 0) #\1)
|
|
|
|
|
(eql (schar *mysql-client-info* 0) #\0))
|
|
|
|
|
(pushnew :mysql-client-v6 cl:*features*))
|
|
|
|
|
(t
|
|
|
|
|
(error "Unknown mysql client version '~A'." *mysql-client-info*)))))
|
|
|
|
|
|
|
|
|
|
charset issues
|
|
|
|
|
|
|
|
|
|
After (finally) connecting, when we first query the database we might get an error like this:
|
|
|
|
|
|
|
|
|
|
debugger invoked on a BABEL-ENCODINGS:INVALID-UTF8-STARTER-BYTE in thread
|
|
|
|
|
#<THREAD "main thread" RUNNING {B3E2151}>: Illegal :UTF-8 character starting at position 18.
|
|
|
|
|
|
|
|
|
|
We can identify the issue like so:
|
|
|
|
|
|
|
|
|
|
(defvar charsets
|
|
|
|
|
"SELECT VARIABLE_NAME, SESSION_VALUE \
|
|
|
|
|
FROM INFORMATION_SCHEMA.SYSTEM_VARIABLES \
|
|
|
|
|
WHERE VARIABLE_NAME LIKE 'character_set_c%' \
|
|
|
|
|
OR VARIABLE_NAME LIKE 'character_set_re%' \
|
|
|
|
|
OR VARIABLE_NAME LIKE 'collation_c%';")
|
|
|
|
|
|
|
|
|
|
;; CHARSETS
|
|
|
|
|
(clsql:query charsets)
|
|
|
|
|
|
|
|
|
|
;; (("COLLATION_CONNECTION" "latin1_swedish_ci")
|
|
|
|
|
;; ("CHARACTER_SET_CONNECTION" "latin1")
|
|
|
|
|
;; ("CHARACTER_SET_RESULTS" "latin1")
|
|
|
|
|
;; ("CHARACTER_SET_CLIENT" "latin1"))
|
|
|
|
|
;; ("VARIABLE_NAME" "SESSION_VALUE")
|
|
|
|
|
|
|
|
|
|
These are actually the default collation and character sets in MYSQL; Smarter mysql drivers would set their charset and collation to utf8 by themseleves, but not clsql! (the collation is set to latin1_swedish_ci by default because David Axmark, one of the co-founders of MYSQL, is Swedish. No, really.)
|
|
|
|
|
|
|
|
|
|
We can fix this on the database connection level by setting names:
|
|
|
|
|
|
|
|
|
|
(clsql:execute-command "SET NAMES 'utf8mb4'")
|
|
|
|
|
|
|
|
|
|
And now when we query charsets again they're utf8!
|
|
|
|
|
|
|
|
|
|
(clsql:query charsets)
|
|
|
|
|
|
|
|
|
|
;; (("COLLATION_CONNECTION" "utf8mb4_general_ci")
|
|
|
|
|
;; ("CHARACTER_SET_CONNECTION" "utf8mb4")
|
|
|
|
|
;; ("CHARACTER_SET_RESULTS" "utf8mb4")
|
|
|
|
|
;; ("CHARACTER_SET_CLIENT" "utf8mb4"))
|
|
|
|
|
;; ("VARIABLE_NAME" "SESSION_VALUE")
|
|
|
|
|
|
|
|
|
|
However, this has to be set per-database connection. Really, we want to be using utf8 by default, because it's fucking 2020 and utf8 has been the standard character encoding for over a decade.
|
|
|
|
|
|
|
|
|
|
You can find you mariadb config files with this shell command:
|
|
|
|
|
|
|
|
|
|
$ mysql --help --verbose | grep -A 1 "Default options"
|
|
|
|
|
|
|
|
|
|
Default options are read from the following files in the given order:
|
|
|
|
|
/etc/my.cnf /etc/mysql/my.cnf ~/.my.cnf
|
|
|
|
|
|
|
|
|
|
... then add add these values under their respective headings in one of those files and hey presto, you're charsetting like it's 2008!
|
|
|
|
|
|
|
|
|
|
[client]
|
|
|
|
|
default-character-set = utf8mb4
|
|
|
|
|
|
|
|
|
|
[mysql]
|
|
|
|
|
default-character-set = utf8mb4
|
|
|
|
|
|
|
|
|
|
[mysqld]
|
|
|
|
|
collation-server = utf8mb4_unicode_ci
|
|
|
|
|
init-connect= 'SET NAMES utf8mb4'
|
|
|
|
|
character-set-server = utf8mb4
|
|
|
|
|
|
|
|
|
|
Now the charset for our initial connection should be set to utf8, and we're free to hack away~ "#;
|
|
|
|
|
|
|
|
|
|
b.iter(|| {
|
|
|
|
|
for ch in input.chars()
|
|
|
|
|
{
|
|
|
|
|
let counter = small.entry(ch).or_insert(0);
|
|
|
|
|
*counter +=1;
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
mod tests;
|
|
|
|
|