Categories
PHP WordPress

Tip: Optimización de consultas SQL en plugins de WordPress

Durante el tiempo que no hubo actividad alguna en este blog y por algunos trabajos que me encomendaron relacionados al desarrollo de plugins y modificación del código de WordPress, he visto algo que se repite casi en todos los plugins que he visto hasta el momento: usan varias consultas pequeñas cuando sólo una puede hacer el trabajo (no tengo idea de porqué hacen las cosas de ese modo).

php:

$a = query("select ID from tabla1 ...");
$b = array();
foreach($a as $v) {
        $b[] = query("select ... from tabla2 where ID = $v");
}

El problema del código mostrado es que se hacen consultas innecesarias a la base de datos cuando en este caso un simple JOIN serviría para el mismo propósito.

Como ejemplo voy a poner dos plugins que acompañaron a este blog desde hace más de un año y por los que el número de consultas SQL aunmentaba en 55 para generar la página principa. Éstos son:

  • Real Fast Latest Comments: En este caso el plugin realiza una consulta para obtener las entradas que recibieron comentarios recientemente y luego itera para mostrar los datos devueltos:

    php:

    function rflc_show_comments($comment_limit = 5, $show_trackbacks = false) {
            global $wpdb;

            if(!$show_trackbacks) {
                    $activity = $wpdb->get_results("SELECT $wpdb->comments.comment_date, $wpdb->comments.comment_author,
                                                    $wpdb->comments.comment_ID, $wpdb->posts.post_title,
                                                    $wpdb->posts.ID FROM $wpdb->comments INNER JOIN
                                                    $wpdb->posts ON $wpdb->posts.ID = $wpdb->comments.comment_post_ID WHERE
                                                    $wpdb->comments.comment_approved = '1' AND $wpdb->comments.comment_type
                                                    NOT LIKE '%back%' ORDER BY $wpdb->comments.comment_date DESC
                                                    LIMIT $comment_limit");
            } else {
                    $activity = $wpdb->get_results("SELECT $wpdb->comments.comment_date, $wpdb->comments.comment_author,
                                                    $wpdb->comments.comment_ID, $wpdb->posts.post_title,
                                                    $wpdb->posts.ID FROM $wpdb->comments INNER JOIN
                                                    $wpdb->posts ON $wpdb->posts.ID = $wpdb->comments.comment_post_ID WHERE
                                                    $wpdb->comments.comment_approved = '1'
                                                    ORDER BY $wpdb->comments.comment_date DESC
                                                    LIMIT $comment_limit");
            }
           
            if($activity) {
                    echo '<ul>';
                    foreach($activity as $comment) {

                            echo '<li>'.wp_specialchars($comment->comment_author).' en: <a href="'. get_permalink($comment->ID) .'#comment-'. $comment->comment_ID .'">'. wp_specialchars($comment->post_title) .'</a></li>' . "\n";

                    }
                    echo '</ul>';
            }
    }

    El problema en este caso es que para mostrar el enlace (permalink) de una entrada, el plugin invoca en cada iteración a la función get_permalink, la misma que hace una nueva consulta (o varias dependiendo de la estructura de permalinks) para recuperar la entrada y formatear el enlace, esto pasa siempre y cuando el argumento pasado no sea un objeto o la entrada ya se encuentre en la caché de objetos.

  • Related Posts: El problema es el mismo que se comenta en el anterior, a continuación muestro la parte relevante:

    php:

    $sql = "SELECT ID, post_title, post_content,"
             . "MATCH (post_name, post_content) "
             . "AGAINST ('$terms') AS score "
             . "FROM $wpdb->posts WHERE "
             . "MATCH (post_name, post_content) "
             . "AGAINST ('$terms') "
             . "AND post_date <= '$now' "
             . "AND (post_status IN ( 'publish',  'static' ) && ID != '$post->ID') ";
    if ($show_pass_post=='false') { $sql .= "AND post_password ='' "; }
    $sql .= "ORDER BY score DESC LIMIT $limit";
    $results = $wpdb->get_results($sql);
    $output = '';
    if ($results) {
            foreach ($results as $result) {
                    $title = stripslashes(apply_filters('the_title', $result->post_title));

                    $permalink = get_permalink($result->ID);

                    $post_content = strip_tags($result->post_content);
                    $post_content = stripslashes($post_content);
                    $output .= $before_title .'<a href="'. $permalink .'" rel="bookmark" title="Permanent Link: ' . $title . '">' . $title . '</a>' . $after_title;

                    if ($show_excerpt=='true') {
                            $words=split(" ",$post_content);
                            $post_strip = join(" ", array_slice($words,0,$len));
                            $output .= $before_post . $post_strip . $after_post;
                    }
            }
            echo $output;
    }

Para evitar este comportamiento pero con algunas posibles consecuencias no deseadas**, se debe recuperar también el campo post_name en la primera consulta de ambos casos y a continuación invocar a la función get_permalink con un objeto como parámetro -- get_permalink($comment); y get_permalink($result); respectivamente.

**: Al usar la función get_permalink con un objeto como parámetro, éste se almacena en el caché de objetos y cualquier función que haga uso de get_post puede mostrar entradas "incompletas". Una forma de evitar esto es recuperar todos los campos de la tabla posts o quitar la entrada almacenada en cache luego de invocar a la función get_permalink.

Categories
ASP.NET

ASP.NET MVC

Siguiendo con esta serie de entradas cortas, acaba de salir ASP.NET 3.5 Extensions Preview, que entre las novedades más importantes incluye la primera versión para el público del Framework MVC desarrollado por Microsoft.

Plantilla para crear un proyecto MVC
Estructura de la aplicación MVC

Habrá que ver que tan bueno es esta implementación del patrón MVC en comparación a otras que ya llevan algo de tiempo en esto como MonoRail y Spring.NET. Aunque con respecto a este tema, el equipo que está tras el desarrollo de MonoRail ya expresó su posición:

Regarding the Microsoft MVC

To The .Net Community,

You are probably wondering how the recently announced Microsoft MVC project will compete with Castle's MonoRail.

We think that any attempt to offer more productive tools, better testability and better separation of concerns is valuable, no matter who is the author. We are certainly pleased to see that Microsoft is delivering something that allows a more agile and productive type of web software development.

We also believe that MonoRail has been providing the same thing for the past two and half years, and will continue to do so. We're grateful that MS has chosen to offer integration points for Monorail and the Castle stack and as soon as it's available we will be working to integrate it with the rest of our projects.

Is MS' MVC better? Worse? Only once we have used both will we be able to tell.

Categories
ASP.NET Seguridad

Videos sobre seguridad en ASP.NET

Por si no lo vieron todavía, una serie de videos introductorios -- en inglés -- sobre seguridad en ASP.NET:

  • Canonicalization: Explica sobre los ACLs e impersonación de usuarios para acceder a recursos, así como usar Server.MapPath para limitar el acceso sólo a rutas que esten dentro del directorio virtual de una aplicación.
  • Cookies: Muestra como detectar la alteración de cookies.
  • Cross-Site Scripting: Explica que es XSS y como prevenir éstos ataques.
  • Regular Expressions: Explica el uso de expresiones regulares para la validación de los datos.
  • SQL Injection: Explica que es SQL Injection y cómo evitar este tipo de ataques usando consultas parametrizadas.
  • Validation Controls

Fuente: Blog de J.D. Meier

Categories
.NET Microsoft Miniposts PHP

Misión Géminis 48 hs.

Para los que puedan estar interesados, Misión Géminis 48 hs. es un concurso de Microsoft para...

... demostrar en 2 etapas qué tanto sabes acerca de .Net y PHP. Durante la primera etapa deberás responder una serie de preguntas de selección múltiple sobre estos lenguajes de programación. Los participantes que contesten correctamente todo el cuestionario, con la posibilidad de múltiples intentos, podrán pasar a la segunda etapa – La Misión. Allí deberás estar preparado para el reto de desarrollar una aplicación web funcional, un sistema de Control Epidemiológico basado en PHP, y migrarla a ASP.NET. Tendrás sólo 48 horas para hacerlo, y los primeros 48 que lo hagan estarán participando por increíbles premios. Los 3 ganadores designados por especialistas calificados recibirán U$D 1.000, U$D 800 y U$D 400, respectivamente.

Para mayor información revisen la página del concurso.

Categories
.NET

Ejecutar sentencias T-SQL durante la instalación de aplicaciones .NET

Anteriormente usaba un método algo "brusco" para conseguir crear la base de datos y el esquema correspondiente durante la instalación de una aplicación. Sin embargo, hace poco acabo de enterarme a través de un blog de una forma más elegante de hacer esto para base de datos SQL Server; lo único que se necesita es crear un instalador personalizado y hacer uso de los ensamblados que provee SQL Server Management Objects:

csharp:

public class SqlScriptInstaller : Installer
{
    private readonly string sqlScript_;

    public SqlScriptInstaller(string sqlScript)
    {
        this.sqlScript_ = sqlScript;
    }

    public SqlScriptInstaller(Stream sqlStream)
    {
        using (StreamReader sr = new StreamReader(sqlStream))
        {
            this.sqlScript_ = sr.ReadToEnd();
        }
    }

    public override void Install(IDictionary stateSaver)
    {
        string connectionString = this.Context.Parameters["ConnectionString"];

        using (SqlConnection conn = new SqlConnection(connectionString))
        {
            ServerConnection serverConn = new ServerConnection(conn);
            Server sqlServer = new Server(serverConn);
            sqlServer.ConnectionContext.ExecuteNonQuery(this.sqlScript_);
        }

        base.Install(stateSaver);
    }
}

El script que acompañe a la aplicación puede incluir múltiples sentencias Transact-SQL (incluyendo la sentencia GO).