Die Abfragesprache für die Neo4J Graphendatenbank ist Cypher, die sich irgendwie eine Mischung aus SQL und JSON darstellt. Das Schöne an der Sprache ist, wie einfach sie zu lernen ist, weil die Struktur sehr klar ist und einfach alles so funktioniert wie man es sich denkt. Komplexe Konstrukte wie GROUP BY aus SQL gibt es nicht und die Aggregatsfunktionen fügen sich sehr viel angenehmer in alles
ein als bei SQL.
Die Grundlegende Struktur einer Abfrage ist auch sehr logisch:
* MATCH
* WHERE
* RETURN
* ORDER BY
* LIMIT
Also ich sage welches Graphen-Gebilde ich such. Dann wird festgelegt welche Eigenschaften es erfüllen soll. Das geschieht anhand der Relations und Attribute der Komponenten, die man vorher fest gelegt hat (einfache statische vorgaben wie Ids kann man sogar schon vorher fest legen wie
z.B. " (e:example{id:1})").
Nun definiert man wie das ResultSet aussehen soll. Nodes, Relations, Attribute, Ergebnisse von Aggregatsfunktionen.. kann man beliebig mischen und auch eine CASE-Anweisung ist vorhanden. Dann Legt noch die Sortierung fest und möglicher Weise eine Limitierung des Resulsets.
Besonders schön sind Funktionen wie collect(), die einem Möglichkeiten bieten, die SQL einen einfach nicht nicht bieten kann.
Gehen wir mal von einer Datenbank mit Benutzern und Gruppen aus.
SQL:
SELECT g.id group_id,
u.id user_id,
u.name user_name
FROM groups g,
users u,
users_groups ug
WHERE ug.group_id = g.id
AND u.id = ug.user_id
ORDER BY g.id
Danach müssen wir durch eine Schleife laufen und immer wenn die Group-Id
sich ändert eine neue Liste für die User aufmachen und die solange füllen
bis die nächste Liste erzeugt wird. Die listen kommen dann in eine Map
wo die Group der Key für ihre Liste ist.
Jeder hat so etwas bestimmt schon mal gemacht, um keine einzelnen Queries für jede Group absetzen zu müssen. Ein großes Query ist schneller als viele kleine, weil weniger Overhead für die Connection-Verwaltung gebraucht wird und auch weniger Objekte erzeugt werden, was immer gut für die Performance
ist.
mit Neo4j geht es sehr viel einfacher.
Cypher:
MATCH (g.group)<-[m:member]-(u:user)
RETURN
g.id as group_id,
collect(u)
ORDER BY g.id
Hiermit erhält man eine Liste aller Groups mit 2 Werten. Der erste Wert ist die Id und der zweite ist die Liste der zur Group gehörigen User. Damit ist keine zusätzliche Iteration über die Ergebnismenge mehr nötig, um solch ein Konstrukt zu erzeugen.
Wenn man nun noch die Anzahl der User pro Group direkt haben möchte
muss man das Query nur minimal anpassen. Kein GROUP BY oder ähnliches.
Cypher:
MATCH (g.group)<-[m:member]-(u:user)
RETURN
g.id as group_id,
count(u) as cnt,
collect(u)
ORDER BY g.id
Komplizierter wird es mit Befehlen wie UNWIND oder FOREACH, aber im Vergleich zu entsprechenden SQL Lösungen sind diese auch noch sehr einfach und unkompliziert.
Ein Blick über den SQL-Tellerrand lohnt auf jeden Fall, wenn man Aufwand und Zeit sparen möchte bei Abfragen, die oft geschachtelte Listen nutzen und man es Leid ist, diese aus den Resultsets wieder zu rekonstruieren.
Nach dem ich den Tag mit der Sound API für Java verbracht habe, hab ich dann für heute noch mal eine Verbesserung für MP4toGIF.com in Angriff genommen. Neben den normalen Farbfiltern wird nun noch ein Vignitierungsfilter als Ergänzung zu den Farbfiltern hinzukommen.
Mit dem Canvas sind solche Effekte sehr einfach umzusetzen und sind dabei noch relativ performant.
function createVignetting(ctx){
var x=Math.round(ctx.canvas.width/2);
var y=Math.round(ctx.canvas.height/2);
var grd=ctx.createRadialGradient(x,y,1,x,y,y-5);
grd.addColorStop(0,'rgba(0,0,0,0)');
grd.addColorStop(1,'rgba(0,0,0,0.8)');
ctx.fillStyle=grd;
ctx.fillRect(0,0,ctx.canvas.width,ctx.canvas.height);
return ctx;
}
In letzter Zeit habe ich CodeMirror lieben gelernt. Ich mag es nicht mit TinyMCE zu arbeiten und normale TextAreas sind für HTML-Coding wirklich schrecklich. Wenn man in einem System HTML-Templates bearbeitet, will man Einrückungen und Syntax-Highlighting.
Mit Code-Mirror hat man da und es ist wirklich leicht zu verwenden. Man hat eine Variable pro Editor und das einbringen von Code von Außen (z.B. das hinzufügen von Bilder-Links durch ein Klick auf ein Bild) ist sehr einfach.
//for codemirror
if(main){
main.replaceSelection(textToInsert);
}
Und es gibt sogar einen Mode für BBCode. Damit wird jetzt das Blog-Modul betrieben, wenn man sich gegen HTML mit TinyMCE entscheidet.
Wer also XML oder HTML Code in seiner Web-Anwendung bearbeiten muss oder seinen Benutzern BBCode anbietet kann das mit CodeMirror wirklich leicht und schnell einbauen.
<script src="/reachableContent/codemirror/lib/codemirror.js"></script>
<link rel="stylesheet" href="/reachableContent/codemirror/lib/codemirror.css">
<script src="/reachableContent/codemirror/mode/xml/xml.js"></script>
<script src="/reachableContent/codemirror/mode/css/css.js"></script>
<script src="/reachableContent/codemirror/mode/javascript/javascript.js"></script>
<script src="/reachableContent/codemirror/mode/htmlmixed/htmlmixed.js"></script>
...
<script type="text/javascript">
var myTextArea=document.getElementById("idTextArea");
var myCodeMirror = CodeMirror.fromTextArea(myTextArea,{tabSize: 4,
indentUnit: 4,
indentWithTabs: true,
lineNumbers: true});
</script>
Auch mit BBCode
Nachdem längere Zeit bei MP4toGIF.com nichts mehr passiert ist, habe ich heute mich noch mal mit neuen Filtern auseinander gesetzt und mir eine Umgebung zusammen gebaut, in der ich mit neuen Filtern experimentieren kann. Es ist schon interessant, wie man mit dem Ändern weniger Parameter sehr verschiedene Effekte erzielen kann.
Die Test-Umgebung findet man unter http://www.annonyme.de/js/filters/. Da kann jeder gerne mal herum experimentieren und versuchen selbst Einstellungen für einen tollen Effekt zu finden.
In den nächsten Tagen und Wochen, werden dann also wohl noch ein paar Filter mehr für MP4toGIF.com entstehen und dort integriert werden.
Die zentrale Render-Function ist sehr einfach aufgebaut:
function render(src, trg, rm, ra, gm, ga, bm, ba, useGreyScale){
var srcData=src.getImageData(0,0,src.canvas.width,src.canvas.height);
var data=srcData.data;
for(var i=0;i<data.length;i+=4){
if(useGreyScale){
var avg = (data[i+0]*rm) + (data[i+1]*gm) + (data[i+2]*bm);
data[i+0]=avg;
data[i+1]=avg;
data[i+2]=avg;
}
else{
data[i+0]=data[i+0]*rm;
data[i+1]=data[i+1]*gm;
data[i+2]=data[i+2]*bm;
}
data[i+0]=Math.abs(data[i+0]+parseInt(ra));
data[i+1]=Math.abs(data[i+1]+parseInt(ga));
data[i+2]=Math.abs(data[i+2]+parseInt(ba));
}
trg.canvas.width=src.canvas.width;
trg.canvas.height=src.canvas.height;
trg.putImageData(srcData,0,0);
}
Invertieren des Bildes ist zum Beispiel: Multiplicator auf 1 und darauf -255 addieren.